Repository: tighten/lambo
Branch: main
Commit: 2fea00e4e457
Files: 107
Total size: 226.1 KB
Directory structure:
gitextract_7pldj4rw/
├── .editorconfig
├── .gitattributes
├── .github/
│ └── workflows/
│ └── run-tests.yml
├── .gitignore
├── .phpcs.xml.dist
├── LICENSE.TXT
├── app/
│ ├── Actions/
│ │ ├── AbortsCommands.php
│ │ ├── Concerns/
│ │ │ ├── InteractsWithComposer.php
│ │ │ ├── InteractsWithGitHub.php
│ │ │ └── InteractsWithNpm.php
│ │ ├── CreateDatabase.php
│ │ ├── CustomizeDotEnv.php
│ │ ├── DisplayHelpScreen.php
│ │ ├── DisplayLamboWelcome.php
│ │ ├── EditConfigFile.php
│ │ ├── GenerateAppKey.php
│ │ ├── InitializeGitHubRepository.php
│ │ ├── InitializeGitRepository.php
│ │ ├── InstallBreeze.php
│ │ ├── InstallJetstream.php
│ │ ├── InstallLaravel.php
│ │ ├── InstallNpmDependencies.php
│ │ ├── MigrateDatabase.php
│ │ ├── OpenInBrowser.php
│ │ ├── OpenInEditor.php
│ │ ├── PushToGitHub.php
│ │ ├── RunAfterScript.php
│ │ ├── UpgradeSavedConfiguration.php
│ │ ├── ValetLink.php
│ │ ├── ValetSecure.php
│ │ ├── ValidateGitHubConfiguration.php
│ │ ├── VerifyDependencies.php
│ │ └── VerifyPathAvailable.php
│ ├── Commands/
│ │ ├── .gitkeep
│ │ ├── Debug.php
│ │ ├── EditAfter.php
│ │ ├── EditConfig.php
│ │ ├── HelpCommand.php
│ │ ├── LamboCommand.php
│ │ └── NewCommand.php
│ ├── Configuration/
│ │ ├── CommandLineConfiguration.php
│ │ ├── LamboConfiguration.php
│ │ ├── SavedConfiguration.php
│ │ ├── SetConfig.php
│ │ └── ShellConfiguration.php
│ ├── ConsoleWriter.php
│ ├── Environment.php
│ ├── Helpers/
│ │ └── GetTimezone.php
│ ├── LamboException.php
│ ├── LogsToConsole.php
│ ├── Options.php
│ ├── Providers/
│ │ └── AppServiceProvider.php
│ ├── Shell.php
│ └── Tools/
│ └── Database.php
├── bin/
│ └── testLambo.sh
├── bootstrap/
│ └── app.php
├── box.json
├── builds/
│ └── lambo
├── composer.json
├── config/
│ ├── app.php
│ └── commands.php
├── lambo
├── phpunit.xml.dist
├── readme.md
├── stubs/
│ ├── after
│ └── config
├── tests/
│ ├── CreatesApplication.php
│ ├── Feature/
│ │ ├── CreateDatabaseTest.php
│ │ ├── CustomizeDotEnvTest.php
│ │ ├── EditConfigFileTest.php
│ │ ├── Fakes/
│ │ │ └── FakeProcess.php
│ │ ├── Fixtures/
│ │ │ ├── .lambo/
│ │ │ │ ├── commented_configuration
│ │ │ │ ├── config
│ │ │ │ └── old_configuration
│ │ │ ├── composer-with-laravel-jetstream.json
│ │ │ ├── composer-without-laravel-jetstream.json
│ │ │ ├── composer.json
│ │ │ ├── package-silent.json
│ │ │ └── package.json
│ │ ├── GenerateAppKeyTest.php
│ │ ├── InitializeGitHubRepositoryTest.php
│ │ ├── InitializeGitRepositoryTest.php
│ │ ├── InstallBreezeTest.php
│ │ ├── InstallJetstreamTest.php
│ │ ├── InstallLaravelTest.php
│ │ ├── InstallNpmDependenciesTest.php
│ │ ├── LamboTestEnvironment.php
│ │ ├── MigrateDatabaseTest.php
│ │ ├── OpenInBrowserTest.php
│ │ ├── OpenInEditorTest.php
│ │ ├── PushToGitHubTest.php
│ │ ├── RunAfterScriptTest.php
│ │ ├── SignatureBuilderTest.php
│ │ ├── UpgradeSavedConfigurationTest.php
│ │ ├── ValetLinkTest.php
│ │ ├── ValetSecureTest.php
│ │ ├── ValidateGitHubConfigurationTest.php
│ │ ├── VerifyDependenciesTest.php
│ │ └── VerifyPathAvailableTest.php
│ ├── TestCase.php
│ └── Unit/
│ ├── CommandLineConfigurationTest.php
│ ├── Fakes/
│ │ └── FakeInput.php
│ ├── GetTimezoneTest.php
│ ├── SavedConfigurationTest.php
│ ├── SetConfigTest.php
│ └── ShellConfigurationTest.php
└── tlint.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.yml]
indent_style = space
indent_size = 2
================================================
FILE: .gitattributes
================================================
* text=auto
.travis.yml export-ignore
================================================
FILE: .github/workflows/run-tests.yml
================================================
name: Run Tests
on:
push:
branches: [ main ]
pull_request:
jobs:
tests:
strategy:
matrix:
os: [Ubuntu, macOS]
php: [8.1, 8.2]
include:
- os: Ubuntu
os-version: ubuntu-latest
- os: macOS
os-version: macos-latest
name: ${{ matrix.os }} - PHP ${{ matrix.php }}
runs-on: ${{ matrix.os-version }}
steps:
- name: Checkout code
uses: actions/checkout@v1
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: posix, dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
coverage: none
- name: Install dependencies
run: composer update --prefer-stable --prefer-dist --no-interaction --no-suggest
- name: Run PHP tests
run: vendor/bin/phpunit
================================================
FILE: .gitignore
================================================
/vendor
.phpunit.result.cache
/storage/framework/
.php_cs.cache
================================================
FILE: .phpcs.xml.dist
================================================
app
config
tests
================================================
FILE: LICENSE.TXT
================================================
The MIT License (MIT)
Copyright (c)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: app/Actions/AbortsCommands.php
================================================
getCommandLine()}'.");
}
throw new LamboException("{$message}");
}
}
}
================================================
FILE: app/Actions/Concerns/InteractsWithComposer.php
================================================
getComposerRequireCommand($package, $forDev);
$composerProcess = app(Shell::class)->execInProject($command);
$this->abortIf(! $composerProcess->isSuccessful(), 'Composer package installation failed.', $composerProcess);
}
protected function getComposerRequireCommand(string $package, bool $forDev): string
{
return sprintf(
'composer require %s%s%s',
$package,
$forDev ? ' --dev' : '',
config('lambo.store.with_output') ? '' : ' --quiet'
);
}
}
================================================
FILE: app/Actions/Concerns/InteractsWithGitHub.php
================================================
installNodeDependencies();
$this->compileNodeDependencies();
}
/**
* @throws \App\LamboException
*/
public function installNodeDependencies(): void
{
$process = app(Shell::class)->execInProject('npm install' . (config('lambo.store.with_output') ? '' : ' --silent'));
$this->abortIf(! $process->isSuccessful(), 'Installation of npm dependencies did not complete successfully', $process);
}
/**
* @throws \App\LamboException
*/
protected function compileNodeDependencies(): void
{
$process = app(Shell::class)->execInProject('npm run build' . (config('lambo.store.with_output') ? '' : ' --silent'));
$this->abortIf(! $process->isSuccessful(), 'Compilation of project assets did not complete successfully', $process);
}
}
================================================
FILE: app/Actions/CreateDatabase.php
================================================
shell = $shell;
$this->database = $database;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
if (! config('lambo.store.create_database')) {
return;
}
$this->consoleWriter->logStep('Creating database');
$db_name = config('lambo.store.database_name');
try {
$databaseCreated = $this->database
->fillFromLamboStore(config('lambo.store'))
->create($db_name);
if (! $databaseCreated) {
$this->consoleWriter->warn($this->failureToCreateError($db_name));
return;
}
} catch (PDOException $e) {
$this->consoleWriter->warn($e->getMessage());
$this->consoleWriter->warn($this->failureToCreateError($db_name));
return;
}
$this->consoleWriter->success("Created a new database '{$db_name}'");
}
protected function failureToCreateError(string $db_name): string
{
return sprintf(
"Failed to create database '%s' using credentials mysql://%s:****@%s:%s>\nYou will need to create the database manually.",
$db_name,
config('lambo.store.database_username'),
config('lambo.store.database_host'),
config('lambo.store.database_port')
);
}
}
================================================
FILE: app/Actions/CustomizeDotEnv.php
================================================
consoleWriter = $consoleWriter;
}
public function __invoke()
{
$this->consoleWriter->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->consoleWriter->success('.env files configured.');
}
public function customize($contents): string
{
return collect(explode("\n", $contents))->transform(function ($item) {
$parts = explode('=', $item, 2);
// Line doesn't contain an equal sign (=); return without modification
if (count($parts) < 2) {
return $item;
}
[$envKey, $envVal] = $parts;
$replace = $this->value($envKey, $envVal);
return "{$envKey}={$replace}";
})->implode("\n");
}
public function value($key, $fallback)
{
$replacements = [
'APP_NAME' => config('lambo.store.project_name'),
'APP_URL' => config('lambo.store.project_url'),
'DB_PORT' => config('lambo.store.database_port'),
'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);
}
}
================================================
FILE: app/Actions/DisplayHelpScreen.php
================================================
text("\n Usage:{$this->createCliStringForCommandUsage()}");
app('console-writer')->text("\n Options (lambo new myApplication):{$this->createCliStringForOptionDescriptions()}");
}
public function createCliStringForOptionDescriptions(): string
{
return collect((new Options())->all())->reduce(function ($carry, $option) {
$flag = isset($option['short'])
? '-' . $option['short'] . ', --' . $option['long']
: ' --' . $option['long'];
$flag .= isset($option['param_description'])
? "={$option['param_description']}"
: '';
return $carry . sprintf("\n %-33s%s", $flag, $option['cli_description']);
});
}
private function createCliStringForCommandUsage(): string
{
return collect([
[
'usage' => 'lambo edit-config [--editor=]',
'description' => 'Create or edit your ~/.lambo/config file',
],
[
'usage' => 'lambo edit-after [--editor=]',
'description' => 'Create or edit your ~/.lambo/after file',
],
[
'usage' => 'lambo new myApplication [options]',
'description' => 'Scaffold a new Laravel application',
],
])->reduce(function ($carry, $command) {
return $carry . sprintf("\n %-40s %s", $command['usage'], $command['description']);
});
}
}
================================================
FILE: app/Actions/DisplayLamboWelcome.php
================================================
Lambo: Super-powered 'laravel new' for Laravel and Valet.";
public function __construct()
{
$this->lamboLogo = str_replace(':version:', config('app.version'), $this->lamboLogo);
}
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-writer')->text("{$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-writer')->text("{$line} ");
}
}
}
================================================
FILE: app/Actions/EditConfigFile.php
================================================
shell = $shell;
}
public function __invoke(string $fileName)
{
$configDir = config('home_dir') . '/.lambo';
$configFilePath = $configDir . '/' . $fileName;
if (! File::isDirectory($configDir)) {
app('console-writer')->note("Configuration directory '{$configDir}' does not exist, creating it now...");
$this->abortIf(! File::makeDirectory($configDir), "I could not create the directory: {$configDir}.");
}
if (! File::isFile($configFilePath)) {
app('console-writer')->note("File '{$configFilePath}' does not exist, creating it now...");
$this->abortIf(! File::put($configFilePath, File::get(base_path("stubs/{$fileName}"))), "I could not create the configuration file: {$configFilePath}.");
}
$process = $this->shell->withTTY()->execIn($configDir, config('lambo.store.editor') . " {$fileName}");
$this->abortIf(! $process->isSuccessful(), "I could not open {$configFilePath} for editing.", $process);
}
}
================================================
FILE: app/Actions/GenerateAppKey.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
$this->consoleWriter->logStep('Setting APP_KEY in .env');
$process = $this->shell->execInProject("php artisan key:generate{$this->withQuiet()}");
$this->abortIf(! $process->isSuccessful(), 'Failed to generated APP_KEY successfully', $process);
$this->consoleWriter->success('APP_KEY has been set.');
}
private function withQuiet()
{
return config('lambo.store.with_output') ? '' : ' --quiet';
}
}
================================================
FILE: app/Actions/InitializeGitHubRepository.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
if (! static::gitHubInitializationRequested()) {
return;
}
$this->consoleWriter->logStep('Initializing GitHub repository');
$process = $this->shell->execInProject(static::getGitHubCreateCommand());
if (! $process->isSuccessful()) {
$this->consoleWriter->warn(self::WARNING_FAILED_TO_CREATE_REPOSITORY);
$this->consoleWriter->warnCommandFailed($process->getCommandLine());
$this->consoleWriter->showOutputErrors($process->getErrorOutput());
config(['lambo.store.push_to_github' => false]);
return;
}
config(['lambo.store.push_to_github' => true]);
$this->consoleWriter->success('Successfully created new GitHub repository');
}
}
================================================
FILE: app/Actions/InitializeGitRepository.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
$this->consoleWriter->logStep('Initializing git repository');
$this->exec(sprintf(
'git init%s',
config('lambo.store.with_output') ? '' : ' --quiet',
));
$this->exec('git add .');
$this->exec(sprintf(
"git commit%s -m '%s'",
config('lambo.store.with_output') ? '' : ' --quiet',
config('lambo.store.commit_message')
));
$this->consoleWriter->success('New git repository initialized.');
}
public function exec($command)
{
$process = $this->shell->execInProject($command);
$this->abortIf(! $process->isSuccessful(), 'Initialization of git repository did not complete successfully.', $process);
}
}
================================================
FILE: app/Actions/InstallBreeze.php
================================================
'blade',
'Vue' => 'vue',
'React' => 'react',
];
private $shell;
private $consoleWriter;
public function __construct(Shell $shell, ConsoleWriter $consoleWriter)
{
$this->shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
if (($stack = config('lambo.store.breeze')) === false) {
return;
}
if (! in_array($stack, array_values(self::VALID_STACKS), true)) {
throw new LamboException("'{$stack}' is not a valid Breeze configuration.");
}
$this->consoleWriter->logStep('Installing Laravel Breeze starter kit');
$this->composerRequire('laravel/breeze');
$this->installBreeze($stack);
$this->installAndCompileNodeDependencies();
$this->consoleWriter->success('Successfully installed Laravel Breeze.');
}
protected function installBreeze($stack): void
{
$process = $this->shell->execInProject(sprintf(
'php artisan breeze:install%s%s',
$stack === 'blade' ? '' : " {$stack}",
config('lambo.store.with_output') ? '' : ' --quiet'
));
$this->abortIf(! $process->isSuccessful(), 'Failed to install Laravel Breeze.', $process);
}
}
================================================
FILE: app/Actions/InstallJetstream.php
================================================
'inertia',
'Livewire' => 'livewire',
];
private ConsoleWriter $consoleWriter;
private Shell $shell;
public function __construct(Shell $shell, ConsoleWriter $consoleWriter)
{
$this->shell = $shell;
$this->consoleWriter = $consoleWriter;
}
/**
* @throws LamboException
*/
public function __invoke(): void
{
if (($stack = config('lambo.store.jetstream')) === false) {
return;
}
if (! in_array($stack, self::VALID_CONFIGURATIONS, true)) {
throw new LamboException("'{$stack}' is not a valid Jetstream configuration.");
}
$this->consoleWriter->logStep('Installing Laravel Jetstream starter kit');
$this->composerRequire('laravel/jetstream');
$this->installJetstream($stack);
$this->installAndCompileNodeDependencies();
$this->consoleWriter->success('Successfully installed Laravel Jetstream.');
}
/**
* @throws LamboException
*/
protected function installJetstream(string $stack): void
{
$configuration = explode(',', $stack);
$installJetstreamProcess = $this->shell->execInProject(sprintf(
'php artisan jetstream:install %s%s%s',
in_array('livewire', $configuration) ? 'livewire' : 'inertia',
in_array('teams', $configuration) ? ' --teams' : '',
config('lambo.store.with_output') ? '' : ' --quiet'
));
$this->abortIf(! $installJetstreamProcess->isSuccessful(), 'Failed to install Laravel Jetstream.', $installJetstreamProcess);
}
}
================================================
FILE: app/Actions/InstallLaravel.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
$this->consoleWriter->logStep('Creating the new Laravel project');
$process = $this->shell->execInRoot(sprintf(
'composer create-project laravel/laravel %s%s --remove-vcs --prefer-dist %s',
config('lambo.store.project_name'),
config('lambo.store.dev') ? ' dev-master' : '',
config('lambo.store.with_output') ? '' : '--quiet'
));
$this->abortIf(! $process->isSuccessful(), 'The laravel installer did not complete successfully.', $process);
$this->consoleWriter->success(sprintf(
"A new application '%s' has been created from the %s branch.",
config('lambo.store.project_name'),
config('lambo.store.dev') ? 'develop' : 'release'
));
}
}
================================================
FILE: app/Actions/InstallNpmDependencies.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
$this->consoleWriter->logStep('Installing node dependencies');
$process = $this->shell->execInProject("npm install{$this->withQuiet()}");
$this->abortIf(! $process->isSuccessful(), 'Installation of npm dependencies did not complete successfully', $process);
$this->consoleWriter->newLine();
$this->consoleWriter->success('Npm dependencies installed.');
}
public function withQuiet()
{
return config('lambo.store.with_output') ? '' : ' --silent';
}
}
================================================
FILE: app/Actions/MigrateDatabase.php
================================================
shell = $shell;
$this->database = $database;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
if (! config('lambo.store.migrate_database')) {
return;
}
$this->consoleWriter->logStep('Running database migrations');
try {
$this->database
->fillFromLamboStore(config('lambo.store'))
->ensureExists(config('lambo.store.database_name'));
$process = $this->shell->execInProject("php artisan migrate{$this->withQuiet()}");
return $process->isSuccessful()
? $this->consoleWriter->success('Database migrated')
: $this->consoleWriter->warn("Failed to run {$process->getCommandLine()}");
} catch (PDOException $e) {
$this->consoleWriter->warn($e->getMessage());
return $this->consoleWriter->warn($this->failureMigrateError());
}
}
protected function failureMigrateError(): string
{
return sprintf(
"Skipping database migration using credentials mysql://%s:****@%s:%s>\nYou will need to run the database migrations manually.",
config('lambo.store.database_username'),
config('lambo.store.database_host'),
config('lambo.store.database_port')
);
}
private function withQuiet()
{
return config('lambo.store.with_output') ? '' : ' --quiet';
}
}
================================================
FILE: app/Actions/OpenInBrowser.php
================================================
shell = $shell;
$this->environment = $environment;
}
public function __invoke()
{
if (config('lambo.store.no_browser')) {
return;
}
app('console-writer')->logStep('Opening in Browser');
if ($this->environment->isMac() && $this->browser()) {
$this->shell->execInProject(sprintf(
'open -a "%s" "%s"',
$this->browser(),
config('lambo.store.project_url')
));
return;
}
$this->shell->execInProject('valet open');
}
public function browser()
{
return config('lambo.store.browser');
}
}
================================================
FILE: app/Actions/OpenInEditor.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
$this->consoleWriter->logStep('Opening In Editor');
$process = $this->shell->withTTY()->execInProject(sprintf('%s .', config('lambo.store.editor')));
$this->abortIf(! $process->isSuccessful(), sprintf('Failed to open editor %s', config('lambo.store.editor')), $process);
$this->consoleWriter->success('Opening your project in ' . config('lambo.store.editor'));
}
}
================================================
FILE: app/Actions/PushToGitHub.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
if (! (config('lambo.store.push_to_github'))) {
return;
}
$this->consoleWriter->logStep('Pushing new project to GitHub');
$branchNameProcess = $this->shell->execInProject('git rev-parse --abbrev-ref HEAD');
if (! $branchNameProcess->isSuccessful()) {
$this->consoleWriter->warn(self::WARNING_UNABLE_TO_GET_BRANCH_NAME);
$this->consoleWriter->warn("Failed to run {$branchNameProcess->getCommandLine()}");
$this->consoleWriter->showOutputErrors($branchNameProcess->getErrorOutput());
return;
}
$process = $this->shell->execInProject("git push -u origin {$branchNameProcess->getOutput()}");
if (! $process->isSuccessful()) {
$this->consoleWriter->warn(self::WARNING_FAILED_TO_PUSH);
$this->consoleWriter->warn("Failed to run {$process->getCommandLine()}");
$this->consoleWriter->showOutputErrors($process->getErrorOutput());
return;
}
$this->consoleWriter->success('Successfully pushed project to GitHub');
}
}
================================================
FILE: app/Actions/RunAfterScript.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
$afterScriptPath = config('home_dir') . '/.lambo/after';
if (! File::isFile($afterScriptPath)) {
return;
}
$this->consoleWriter->logStep('Running after script');
$this->shell->withTTY();
$process = $this->shell->execInProject(sprintf(
'env PROJECTPATH=%s sh %s',
config('lambo.store.project_path'),
$afterScriptPath
));
$this->abortIf(! $process->isSuccessful(), 'After file did not complete successfully', $process);
$this->consoleWriter->success('After script has completed.');
}
}
================================================
FILE: app/Actions/UpgradeSavedConfiguration.php
================================================
[
'commented' => false,
'default' => 'false',
'description' => [
'Run the standard Laravel database migrations.',
'Possible values:',
' true, 1, "yes" or "on"',
' false (default), 0, "no" or "off"',
],
],
'DB_HOST' => [
'commented' => true,
'default' => '127.0.0.1',
'description' => [
'The database host. Defaults to 127.0.0.1.',
],
],
'DB_PORT' => [
'commented' => true,
'default' => '',
'description' => [
'The database port. Defaults to 3306.',
],
],
'DB_NAME' => [
'commented' => true,
'default' => '',
'description' => [
'The database name. Defaults to the project name.',
],
],
];
public function __construct()
{
$this->configDir = config('home_dir') . '/.lambo';
$this->configFilePath = "{$this->configDir}/config";
$this->lastVersionUpdateFilePath = "{$this->configDir}/.last_version_update";
}
public function __invoke(): bool
{
if (! $this->shouldUpgrade()) {
return false;
}
$savedConfiguration = File::get($this->configFilePath);
File::move($this->configFilePath, $this->configFilePath . '.' . Carbon::now()->toDateTimeLocalString());
File::put($this->configFilePath, $this->upgrade($savedConfiguration, $this->removedConfigurationKeys, $this->newConfiguration));
File::delete($this->lastVersionUpdateFilePath);
File::put($this->lastVersionUpdateFilePath, $this->configurationVersion);
return true;
}
public function upgrade(string $savedConfiguration, array $removedConfigurationKeys, array $newConfiguration = []): string
{
return implode(PHP_EOL, [
$this->commentRemovedConfiguration($savedConfiguration, $removedConfigurationKeys),
"\n# ------------------------------------------------------------------------------",
'# ' . Carbon::now(config('app.timezone'))->format('j-M-Y g:i a') . ' (auto-generated by Lambo):',
'# ------------------------------------------------------------------------------',
$this->summarizeComments(),
$this->addNewConfiguration($newConfiguration),
]);
}
private function shouldUpgrade(): bool
{
if (! File::isFile($this->configFilePath)) {
return false;
}
$localVersion = File::isFile($this->lastVersionUpdateFilePath)
? (int)File::get($this->lastVersionUpdateFilePath)
: 0;
if ($localVersion < $this->configurationVersion) {
return true;
}
return false;
}
private function commentRemovedConfiguration(string $savedConfiguration, array $oldConfigurationKeys): string
{
return collect(explode("\n", $savedConfiguration))->transform(function ($item) use ($oldConfigurationKeys) {
$matched = collect($oldConfigurationKeys)->reduce(function ($carry, $oldKey) use ($item) {
return $carry || Str::of($item)->startsWith($oldKey);
}, false);
if ($matched) {
$this->commented[] = $item;
return "#{$item}";
}
return $item;
})->implode("\n");
}
private function summarizeComments(): string
{
if (count($this->commented) < 1) {
return '';
}
return implode(PHP_EOL, [
'# Lambo has commented out the following configuration items as they',
'# are no-longer used. You may safely remove them:',
collect($this->commented)->reduce(function ($carry, $item) {
return "{$carry}# {$item}\n";
}, '')
]);
}
private function addNewConfiguration(array $newConfiguration): string
{
if (count($newConfiguration) < 1) {
return '';
}
return collect(array_keys($newConfiguration))->reduce(function ($carry, $key) use ($newConfiguration) {
$description = collect($newConfiguration[$key]['description'])->reduce(function ($carry, $item) {
return "{$carry}# {$item}\n";
}, '');
$configurationItem = sprintf(
"%s%s=%s\n\n",
$newConfiguration[$key]['commented'] ? '#' : '',
$key,
$newConfiguration[$key]['default']
);
return "{$carry}{$description}{$configurationItem}";
}, "# Lambo has introduced new configuration options. They have been added here\n# with sensible defaults; however, you should review them.\n#\n");
}
}
================================================
FILE: app/Actions/ValetLink.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
if (! config('lambo.store.valet_link')) {
return;
}
$this->consoleWriter->logStep('Running valet link');
$process = $this->shell->execInProject('valet link');
$this->abortIf(! $process->isSuccessful(), 'valet link did not complete successfully', $process);
$this->consoleWriter->success('valet link successful');
}
}
================================================
FILE: app/Actions/ValetSecure.php
================================================
shell = $shell;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
if (! config('lambo.store.valet_secure')) {
return;
}
$this->consoleWriter->logStep('Running valet secure');
$process = $this->shell->execInProject('valet secure');
$this->abortIf(! $process->isSuccessful(), 'valet secure did not complete successfully', $process);
$this->consoleWriter->success('valet secure successful');
}
}
================================================
FILE: app/Actions/ValidateGitHubConfiguration.php
================================================
https://github.com/cli/cli#installation>',
' - https://github.com/github/hub>',
];
public const INSTRUCTIONS_GH_NOT_AUTHENTICATED = [
'You are not logged into GitHub. Please run gh auth login.',
'For more information, please visit, https://cli.github.com/manual/gh_auth_login>',
];
public const QUESTION_SHOULD_CONTINUE = 'Would you like Lambo to continue without GitHub repository creation?';
public const SELECTED_GITHUB_TOOL_MESSAGE_PATTERN = "Using the '%s' command for GitHub configuration.";
private $consoleWriter;
private $shell;
public function __construct(Shell $shell, ConsoleWriter $consoleWriter)
{
$this->consoleWriter = $consoleWriter;
$this->shell = $shell;
}
public function __invoke()
{
if (! static::gitHubInitializationRequested()) {
config(['lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB => false]);
return;
}
if (! static::gitHubToolingInstalled()) {
$this->consoleWriter->warn(self::WARNING_UNABLE_TO_CREATE_REPOSITORY);
$this->consoleWriter->text(self::INSTRUCTIONS_GITHUB_TOOLING_MISSING);
$this->askToContinueWithoutGitHubSetup();
return;
}
if (static::hubInstalled()) {
$this->consoleWriter->note(sprintf(self::SELECTED_GITHUB_TOOL_MESSAGE_PATTERN, 'hub'));
return;
}
if (! $this->ghAuthenticated()) {
$this->consoleWriter->warn(self::WARNING_UNABLE_TO_CREATE_REPOSITORY);
$this->consoleWriter->text(self::INSTRUCTIONS_GH_NOT_AUTHENTICATED);
$this->askToContinueWithoutGitHubSetup();
return;
}
$this->consoleWriter->note(sprintf(self::SELECTED_GITHUB_TOOL_MESSAGE_PATTERN, 'gh'));
}
private function askToContinueWithoutGitHubSetup()
{
config(['lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB => false]);
if (! app('console')->confirm(self::QUESTION_SHOULD_CONTINUE)) {
exit;
}
}
private function ghAuthenticated(): bool
{
$process = $this->shell->execQuietly('gh auth status');
return $process->isSuccessful();
}
}
================================================
FILE: app/Actions/VerifyDependencies.php
================================================
'composer',
'label' => 'Composer',
'instructions_url' => 'https://getcomposer.org',
],
[
'command' => 'valet',
'label' => 'Laravel valet',
'instructions_url' => 'https://laravel.com/docs/valet',
],
[
'command' => 'git',
'label' => 'Git version control',
'instructions_url' => 'https://git-scm.com',
],
];
private $optionalDependencies = [
[
'command' => 'hub',
'label' => 'Unofficial GitHub command line tool',
'instructions_url' => 'https://github.com/github/hub',
],
[
'command' => 'gh',
'label' => 'Official GitHub command line tool',
'instructions_url' => 'https://cli.github.com/',
],
];
public function __construct(ExecutableFinder $finder, ConsoleWriter $consoleWriter)
{
$this->finder = $finder;
$this->consoleWriter = $consoleWriter;
}
public function __invoke()
{
$this->consoleWriter->logStep('Verifying dependencies');
$this->consoleWriter->text('Optional dependencies');
$this->consoleWriter->newLine();
foreach ($this->optionalDependencies as $optionalDependency) {
list($command, $label, $instructionsUrl) = array_values($optionalDependency);
if (($installedDependency = $this->finder->find($command)) === null) {
$this->consoleWriter->note("{$label}, an optional dependency, is missing. You can find installation instructions at:\n {$instructionsUrl}>");
config(["lambo.store.tools.{$command}" => false]);
} else {
$this->consoleWriter->success("{$label} found at:\n {$installedDependency}>");
config(["lambo.store.tools.{$command}" => true]);
}
}
$this->consoleWriter->newLine();
$this->consoleWriter->text('Required dependencies');
$this->consoleWriter->newLine();
$this->abortIf(
collect($this->dependencies)->reduce(function ($carry, $dependency) {
list($command, $label, $instructionsUrl) = array_values($dependency);
if (($installedDependency = $this->finder->find($command)) === null) {
$this->consoleWriter->warn("{$label} is missing. You can find installation instructions at:\n {$instructionsUrl}>");
return true;
}
$this->consoleWriter->success("{$label} found at:\n {$installedDependency}>");
return $carry ?? false;
}),
'Please install missing dependencies and try again.'
);
}
}
================================================
FILE: app/Actions/VerifyPathAvailable.php
================================================
logStep('Verifying path availability');
$rootPath = config('lambo.store.root_path');
if (! File::isDirectory($rootPath)) {
throw new LamboException("{$rootPath} is not a directory.");
}
$projectPath = config('lambo.store.project_path');
if (empty($projectPath)) {
throw new LamboException("Configuration 'lambo.store.project_path' cannot be null or an empty string.");
}
if (File::isDirectory($projectPath)) {
if (! config('lambo.store.force_create')) {
throw new LamboException("{$projectPath} is already a directory.");
}
if (! File::deleteDirectory($projectPath)) {
throw new LamboException("{$projectPath} is already a directory and, although the force option was specified, deletion failed.");
}
}
app('console-writer')->success("Directory '{$projectPath}' is available.");
}
}
================================================
FILE: app/Commands/.gitkeep
================================================
================================================
FILE: app/Commands/Debug.php
================================================
filter(function ($value, $key) use ($filter) {
return is_null($filter) || in_array($key, $filter);
})
->map(function ($value, $key) use ($keyPrefix) {
$type = gettype($value);
if (is_string($value)) {
$value = empty($value)
? ConsoleWriter::formatString('""', ConsoleWriter::BLUE)
: ConsoleWriter::formatString($value, ConsoleWriter::BLUE);
}
if (is_integer($value)) {
$value = ConsoleWriter::formatString($value, ConsoleWriter::MAGENTA);
}
if (is_bool($value)) {
$value = $value
? ConsoleWriter::formatString('true', ConsoleWriter::GREEN)
: ConsoleWriter::formatString('false', ConsoleWriter::RED);
}
if (is_null($value)) {
$value = '';
}
return [$keyPrefix . $key, "({$type})", $value];
})->values()->toArray();
$this->consoleWriter->table($headers ?: ['key', 'type', 'value'], $rows);
}
protected function debugReport(): void
{
$this->consoleWriter->panel('Debug', 'Start', 'fg=black;bg=white');
$this->consoleWriter->sectionTitle('Computed configuration');
$this->consoleWriter->text([
'The following is the configuration lambo has computed by merging:',
]);
$this->consoleWriter->listing([
'command line parameters',
'saved configuration',
'shell environment variables.',
]);
$this->configToTable();
$this->consoleWriter->sectionTitle('Pre-flight Configuration');
$this->consoleWriter->newLine();
$this->consoleWriter->text('Raw commmand line:');
$this->arrayToTable(
$_SERVER['argv'],
);
$this->consoleWriter->text('Command line arguments:');
$this->arrayToTable($this->arguments());
$this->consoleWriter->text('Command line options:');
$this->arrayToTable(
$this->options(),
[
'editor',
'path',
'message',
'github',
'gh-public',
'gh-description',
'gh-homepage',
'gh-org',
'breeze',
'jetstream',
'browser',
'dbhost',
'dbport',
'dbname',
'dbuser',
'dbpassword',
'create-db',
'migrate-db',
'force',
'link',
'secure',
'quiet',
'dev',
'full',
'projectName',
],
'--'
);
$this->consoleWriter->text('Saved configuration:');
$savedConfig = [];
if (File::isFile(config('home_dir') . '/.lambo/config')) {
$savedConfig = Dotenv::createMutable(config('home_dir') . '/.lambo', 'config')->load();
}
$this->arrayToTable(
$savedConfig,
[
'CODEEDITOR',
'MESSAGE',
'PROJECTPATH',
'BROWSER',
'FRONTEND',
'DB_PORT',
'DB_NAME',
'DB_USERNAME',
'DB_PASSWORD',
'CREATE_DATABASE',
'LINK',
'SECURE',
'QUIET',
'WITH_OUTPUT',
'DEVELOP',
'FULL',
'WITH_TEAMS',
]
);
$this->consoleWriter->text('Shell environment variables:');
$this->arrayToTable($_SERVER, ['EDITOR'], '$');
$this->logTimezoneData();
$this->consoleWriter->panel('Debug', 'End', 'fg=black;bg=white');
}
protected function configToTable(): void
{
$config = Arr::prepend(config('lambo.store'), config('home_dir'), 'home_dir');
$this->arrayToTable($this->dotFlatten('lambo.store', $config));
}
private function dotFlatten($prefix, $array): array
{
$result = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$result = array_merge($result, $this->dotFlatten($prefix . '.' . $key, $value));
} else {
$result[$prefix . '.' . $key] = $value;
}
}
return $result;
}
protected function logTimezoneData()
{
$this->consoleWriter->sectionTitle('Timezone configuration');
$this->consoleWriter->newLine();
$this->consoleWriter->text('System settings');
$this->arrayToTable([
'OS Config ("/etc/localtime")' => exec('/bin/ls -l /etc/localtime|/usr/bin/cut -d"/" -f8-'),
"ini_get('date.timezone')" => ini_get('date.timezone') ?: 'Not configured',
'IntlTimeZone::createDefault()' => IntlTimeZone::createDefault()->getID(),
'date_default_timezone_get()' => date_default_timezone_get(),
'config->get("app.timezone")' => config()->get('app.timezone'),
]);
$this->consoleWriter->text('Carbon');
$this->arrayToTable([
// UTC, GMT, Atlantic/Azores
'Carbon (Timezone identifier)' => Carbon::now()->format('e'),
// 1 if Daylight Saving Time, 0 otherwise.
'Carbon (Daylight savings)' => (bool)Carbon::now()->format('I'),
// Difference to Greenwich time (GMT)
'Carbon (Difference to GMT w/O)' => Carbon::now()->format('O'), // +0200'Carbon (Difference to GMT w/P)' => Carbon::now()->format('P'), // +02:00
// Examples: EST, MDT
'Carbon (tz abbreviation)' => Carbon::now()->format('T'),
]);
}
}
================================================
FILE: app/Commands/EditAfter.php
================================================
EDITOR or the system default if none is specified.}';
protected $description = 'Edit Config File. A new config file is created if one does not already exist.';
public function handle()
{
app()->bind('console', function () {
return $this;
});
$commandLineConfiguration = new CommandLineConfiguration([
'editor' => LamboConfiguration::EDITOR,
]);
$savedConfiguration = new SavedConfiguration([
'CODEEDITOR' => LamboConfiguration::EDITOR,
]);
$shellConfiguration = new ShellConfiguration([
'EDITOR' => LamboConfiguration::EDITOR,
]);
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$shellConfiguration,
app('console-writer'),
$this->input
))([
LamboConfiguration::EDITOR => 'nano',
]);
try {
app(EditConfigFile::class)('after');
} catch (LamboException $e) {
app('console-writer')->exception($e->getMessage());
}
}
}
================================================
FILE: app/Commands/EditConfig.php
================================================
EDITOR or the system default if none is specified.}';
protected $description = 'Edit Config File. A new config file is created if one does not already exist.';
public function handle()
{
app()->bind('console', function () {
return $this;
});
$commandLineConfiguration = new CommandLineConfiguration([
'editor' => LamboConfiguration::EDITOR,
]);
$savedConfiguration = new SavedConfiguration([
'CODEEDITOR' => LamboConfiguration::EDITOR,
]);
$shellConfiguration = new ShellConfiguration([
'EDITOR' => LamboConfiguration::EDITOR,
]);
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$shellConfiguration,
app('console-writer'),
$this->input
))([
LamboConfiguration::EDITOR => 'nano',
]);
try {
app(EditConfigFile::class)('config');
} catch (LamboException $e) {
app('console-writer')->exception($e->getMessage());
}
}
}
================================================
FILE: app/Commands/HelpCommand.php
================================================
singleton(ConsoleWriter::class, function () use ($input, $output) {
return new ConsoleWriter($input, $output);
});
app()->alias(ConsoleWriter::class, 'console-writer');
return parent::run($input, $output);
}
}
================================================
FILE: app/Commands/NewCommand.php
================================================
signature = $this->buildSignature();
parent::__construct();
app()->bind('console', function () {
return $this;
});
}
public function buildSignature()
{
return collect((new Options())->all())->reduce(
function ($carry, $option) {
return $carry . $this->buildSignatureOption($option);
},
"new\n{projectName? : Name of the Laravel project}"
);
}
public function buildSignatureOption($option): string
{
$commandlineOption = isset($option['short']) ? ($option['short'] . '|' . $option['long']) : $option['long'];
if (isset($option['param_description'])) {
$commandlineOption .= '=' . ($option['default'] ?? '');
}
return "\n{--{$commandlineOption} : {$option['cli_description']}}";
}
public function handle()
{
app(DisplayLamboWelcome::class)();
if (! $this->argument('projectName')) {
app(DisplayHelpScreen::class)();
exit;
}
$this->setConsoleWriter();
$this->setConfig();
if (app(UpgradeSavedConfiguration::class)()) {
$this->consoleWriter->newLine();
$this->consoleWriter->note('Your Lambo configuration (~/.lambo/config) has been updated.');
$this->consoleWriter->note('Please review the changes then run lambo again.');
if ($this->confirm(sprintf('Review the changes now in %s?', config('lambo.store.editor')))) {
app(EditConfigFile::class)('config');
}
return;
}
$this->consoleWriter->sectionTitle("Creating a new Laravel app '{$this->argument('projectName')}'");
try {
app(VerifyDependencies::class)();
app(ValidateGitHubConfiguration::class)();
app(VerifyPathAvailable::class)();
app(InstallLaravel::class)();
app(CustomizeDotEnv::class)();
app(GenerateAppKey::class)();
app(InstallBreeze::class)();
app(InstallJetstream::class)();
app(CreateDatabase::class)();
app(MigrateDatabase::class)();
app(InitializeGitRepository::class)();
app(RunAfterScript::class)();
app(InitializeGitHubRepository::class)();
app(PushToGitHub::class)();
app(ValetLink::class)();
app(ValetSecure::class)();
app(OpenInEditor::class)();
app(OpenInBrowser::class)();
} catch (LamboException $e) {
$this->consoleWriter->exception($e->getMessage());
exit;
}
$this->consoleWriter->newLine();
$this->consoleWriter->text([
'Done, happy coding!>',
'Lambo is brought to you by the lovely folks at Tighten>.',
]);
$this->consoleWriter->newLine();
}
protected function setConsoleWriter()
{
$this->consoleWriter = app(ConsoleWriter::class);
}
private function setConfig(): void
{
config(['lambo.store' => []]); // @todo remove if debug code is removed.
$commandLineConfiguration = new CommandLineConfiguration([
'editor' => LamboConfiguration::EDITOR,
'message' => LamboConfiguration::COMMIT_MESSAGE,
'path' => LamboConfiguration::ROOT_PATH,
'browser' => LamboConfiguration::BROWSER,
'frontend' => LamboConfiguration::FRONTEND_FRAMEWORK,
'dbhost' => LamboConfiguration::DATABASE_HOST,
'dbport' => LamboConfiguration::DATABASE_PORT,
'dbname' => LamboConfiguration::DATABASE_NAME,
'dbuser' => LamboConfiguration::DATABASE_USERNAME,
'dbpassword' => LamboConfiguration::DATABASE_PASSWORD,
'create-db' => LamboConfiguration::CREATE_DATABASE,
'force' => LamboConfiguration::FORCE_CREATE,
'migrate-db' => LamboConfiguration::MIGRATE_DATABASE,
'link' => LamboConfiguration::VALET_LINK,
'secure' => LamboConfiguration::VALET_SECURE,
'with-output' => LamboConfiguration::WITH_OUTPUT,
'dev' => LamboConfiguration::USE_DEVELOP_BRANCH,
'full' => LamboConfiguration::FULL,
'github' => LamboConfiguration::INITIALIZE_GITHUB,
'gh-public' => LamboConfiguration::GITHUB_PUBLIC,
'gh-description' => LamboConfiguration::GITHUB_DESCRIPTION,
'gh-homepage' => LamboConfiguration::GITHUB_HOMEPAGE,
'gh-org' => LamboConfiguration::GITHUB_ORGANIZATION,
'projectName' => LamboConfiguration::PROJECT_NAME,
'breeze' => LamboConfiguration::BREEZE,
'jetstream' => LamboConfiguration::JETSTREAM,
]);
$savedConfiguration = new SavedConfiguration([
'PROJECTPATH' => LamboConfiguration::ROOT_PATH,
'MESSAGE' => LamboConfiguration::COMMIT_MESSAGE,
'DEVELOP' => LamboConfiguration::USE_DEVELOP_BRANCH,
'CODEEDITOR' => LamboConfiguration::EDITOR,
'BROWSER' => LamboConfiguration::BROWSER,
'DB_HOST' => LamboConfiguration::DATABASE_HOST,
'DB_PORT' => LamboConfiguration::DATABASE_PORT,
'DB_NAME' => LamboConfiguration::DATABASE_NAME,
'DB_USERNAME' => LamboConfiguration::DATABASE_USERNAME,
'DB_PASSWORD' => LamboConfiguration::DATABASE_PASSWORD,
'CREATE_DATABASE' => LamboConfiguration::CREATE_DATABASE,
'MIGRATE_DATABASE' => LamboConfiguration::MIGRATE_DATABASE,
'LINK' => LamboConfiguration::VALET_LINK,
'SECURE' => LamboConfiguration::VALET_SECURE,
]);
$shellConfiguration = new ShellConfiguration([
'EDITOR' => LamboConfiguration::EDITOR,
]);
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$shellConfiguration,
$this->consoleWriter,
$this->input
))([
LamboConfiguration::COMMAND => self::class,
LamboConfiguration::EDITOR => 'nano',
LamboConfiguration::COMMIT_MESSAGE => 'Initial commit',
LamboConfiguration::ROOT_PATH => getcwd(),
LamboConfiguration::BROWSER => null,
LamboConfiguration::DATABASE_HOST => '127.0.0.1',
LamboConfiguration::DATABASE_PORT => 3306,
LamboConfiguration::DATABASE_NAME => $this->argument('projectName'),
LamboConfiguration::DATABASE_USERNAME => 'root',
LamboConfiguration::DATABASE_PASSWORD => '',
LamboConfiguration::CREATE_DATABASE => false,
LamboConfiguration::FORCE_CREATE => false,
LamboConfiguration::MIGRATE_DATABASE => false,
LamboConfiguration::VALET_LINK => false,
LamboConfiguration::VALET_SECURE => false,
LamboConfiguration::WITH_OUTPUT => false,
LamboConfiguration::USE_DEVELOP_BRANCH => false,
LamboConfiguration::FULL => false,
LamboConfiguration::INITIALIZE_GITHUB => false,
LamboConfiguration::GITHUB_PUBLIC => false,
LamboConfiguration::PROJECT_NAME => null,
LamboConfiguration::GITHUB_DESCRIPTION => null,
LamboConfiguration::GITHUB_HOMEPAGE => null,
LamboConfiguration::GITHUB_ORGANIZATION => null,
LamboConfiguration::BREEZE => false,
LamboConfiguration::JETSTREAM => false,
LamboConfiguration::TLD => null,
]);
if ($this->consoleWriter->isDebug()) {
$this->debugReport();
}
}
}
================================================
FILE: app/Configuration/CommandLineConfiguration.php
================================================
options();
foreach (app('console')->arguments() as $key => $value) {
$commandLineConfiguration[$key] = $value;
}
return $commandLineConfiguration;
}
}
================================================
FILE: app/Configuration/LamboConfiguration.php
================================================
getSettings();
collect($keyMap)->each(function ($item, $key) use ($settings) {
$this->$item = $this->get($key, $settings);
});
}
abstract protected function getSettings(): array;
protected function get(string $key, array $array)
{
if (array_key_exists($key, $array)) {
if ($array[$key] === '') {
return null;
}
if (in_array(Str::lower($array[$key]), ['1', 'true', 'on', 'yes'])) {
return true;
}
if (in_array(Str::lower($array[$key]), ['0', 'false', 'off', 'no'])) {
return false;
}
return $array[$key];
}
return null;
}
public function __get($name)
{
return null;
}
}
================================================
FILE: app/Configuration/SavedConfiguration.php
================================================
load();
}
}
================================================
FILE: app/Configuration/SetConfig.php
================================================
commandLineConfiguration = $commandLineConfiguration;
$this->savedConfiguration = $savedConfiguration;
$this->shellConfiguration = $shellConfiguration;
$this->consoleWriter = $consoleWriter;
$this->commandLineInput = array_filter($commandLineOptions->getOptions(), function ($value, $key) use ($commandLineOptions) {
return $commandLineOptions->hasParameterOption("--{$key}");
}, ARRAY_FILTER_USE_BOTH);
}
public function __invoke($defaultConfiguration)
{
foreach ($defaultConfiguration as $configurationKey => $default) {
$methodName = 'get' . Str::of($configurationKey)->studly();
if (method_exists($this, $methodName)) {
config(["lambo.store.{$configurationKey}" => $this->$methodName($configurationKey, $default)]);
} else {
config(["lambo.store.{$configurationKey}" => $this->get($configurationKey, $default)]);
}
}
// If we're in the "new" command, generate a few config items which
// require others to be set above first.
if (config('lambo.store.command') === NewCommand::class) {
$projectPath = config('lambo.store.root_path') . '/' . config('lambo.store.project_name');
config(['lambo.store.project_path' => $projectPath]);
config(['lambo.store.project_url' => $this->getProjectURL()]);
}
if (config('lambo.store.full')) {
foreach ($this->fullFlags as $fullFlag) {
config(["lambo.store.{$fullFlag}" => true]);
}
}
}
private function get(string $configurationKey, $default)
{
if (isset($this->commandLineConfiguration->$configurationKey)) {
return $this->commandLineConfiguration->$configurationKey;
}
if (isset($this->savedConfiguration->$configurationKey)) {
return $this->savedConfiguration->$configurationKey;
}
if (isset($this->shellConfiguration->$configurationKey)) {
return $this->shellConfiguration->$configurationKey;
}
return $default;
}
private function getTld(): string
{
$valetConfig = config('home_dir') . '/.config/valet/config.json';
$legacyValetConfig = config('home_dir') . '/.valet/config.json';
if (File::isFile($valetConfig)) {
return json_decode(File::get($valetConfig))->tld;
}
if (File::isFile($legacyValetConfig)) {
return json_decode(File::get($legacyValetConfig))->domain;
}
throw new LamboException(
implode(PHP_EOL, [
'Unable to find valet domain (tld) configuration.',
'No Valet configuration located at either of the following locations:',
"- {$valetConfig}",
"- {$legacyValetConfig}",
])
);
}
private function getRootPath(string $key, $default)
{
$configuredKeyValue = $this->get($key, $default);
return ($configuredKeyValue === $default)
? $default
: str_replace('~', config('home_dir'), $configuredKeyValue);
}
private function getDatabaseName(string $key, $default)
{
return str_replace('-', '_', $this->get($key, $default));
}
private function getProjectURL(): string
{
return sprintf(
'http%s://%s.%s',
config('lambo.store.valet_secure') ? 's' : '',
config('lambo.store.project_name'),
config('lambo.store.tld')
);
}
private function getMigrateDatabase(string $key, $default)
{
if ($this->commandLineConfiguration->inertia || $this->commandLineConfiguration->livewire) {
return true;
}
return $this->get($key, $default);
}
private function getWithOutput(string $key, $default): bool
{
if ($this->consoleWriter->getVerbosity() > SymfonyStyle::VERBOSITY_NORMAL) {
return true;
}
return $this->get($key, $default);
}
private function getBreeze(string $key, $default)
{
$this->ensureOnlyOneStarterKitSelected();
if (! Arr::has($this->commandLineInput, 'breeze')) {
return false;
}
config(['lambo.store.jetstream' => false]);
return in_array($this->commandLineInput['breeze'], InstallBreeze::VALID_STACKS)
? $this->commandLineInput['breeze']
: $this->configureBreezeStack();
}
private function getJetstream(string $key, $default)
{
$this->ensureOnlyOneStarterKitSelected();
if (! Arr::has($this->commandLineInput, 'jetstream')) {
return false;
}
config(['lambo.store.breeze' => false]);
return in_array($this->commandLineInput['jetstream'], InstallJetstream::VALID_CONFIGURATIONS)
? $this->commandLineInput['jetstream']
: $this->configureJetstreamStack();
}
private function configureBreezeStack(): string
{
$this->consoleWriter->note("Laravel Breeze does not provide a '{$this->commandLineInput['breeze']}'> front-end.");
$choice = $this->consoleWriter->choice('Please choose one of the following', array_keys(InstallBreeze::VALID_STACKS));
$this->consoleWriter->ok("Using Laravel Breeze with a {$choice} front-end.");
return Str::lower($choice);
}
private function configureJetstreamStack(): string
{
$this->consoleWriter->note("'{$this->commandLineInput['jetstream']}'> is not a valid Laravel Jetstream configuration.");
$stack = $this->consoleWriter->choice('Please choose a front-end', array_keys(InstallJetstream::VALID_STACKS));
$teams = $this->consoleWriter->confirm('Would you like to use teams?');
$this->consoleWriter->ok(sprintf('Using %s%s.', $stack, $teams ? ' and teams' : ' without teams'));
return InstallJetstream::VALID_STACKS[$stack] . ($teams ? ',teams' : '');
}
private function ensureOnlyOneStarterKitSelected(): void
{
if (Arr::has($this->commandLineInput, ['breeze', 'jetstream'])) {
$this->consoleWriter->newLine();
$this->consoleWriter->note('Only one starter-kit may be configured.');
$choice = $this->consoleWriter->choice('Please choose a starter-kit:', [
'None',
'Laravel Breeze',
'Laravel Jetstream',
], 0);
switch ($choice) {
case 'Laravel Breeze':
unset($this->commandLineConfiguration->jetstream);
Arr::forget($this->commandLineInput, 'jetstream');
$this->consoleWriter->ok('Using Laravel Breeze');
break;
case 'Laravel Jetstream':
Arr::forget($this->commandLineInput, 'breeze');
unset($this->commandLineConfiguration->breeze);
$this->consoleWriter->ok('Using Laravel Jetstream');
break;
case 'None':
Arr::forget($this->commandLineInput, 'jetstream');
unset($this->commandLineConfiguration->jetstream);
Arr::forget($this->commandLineInput, 'breeze');
unset($this->commandLineConfiguration->breeze);
$this->consoleWriter->ok('Skipping starter-kit installation.');
break;
}
}
}
}
================================================
FILE: app/Configuration/ShellConfiguration.php
================================================
{$string}>";
}
public function panel(string $prefix, string $message, string $style)
{
parent::block($message, $prefix, $style, ' ', true, false);
}
public function sectionTitle($sectionTitle)
{
$this->newLine();
$this->text([
"{$sectionTitle}>",
'' . str_repeat('#', strlen($sectionTitle)) . '>',
]);
}
public function logStep($message)
{
parent::block($message, null, 'fg=yellow;bg=default', ' // ', false, false);
}
public function exec(string $command)
{
$this->labeledLine('EXEC', $command, 'bg=blue;fg=black');
}
public function success($message, $label = 'PASS'): void
{
$this->labeledLine($label, $message, 'fg=black;bg=green');
}
public function ok($message): void
{
$this->success($message, ' OK ');
}
public function note($message, $label = 'NOTE'): void
{
$this->labeledLine($label, $message, 'fg=black;bg=yellow');
}
public function warn($message, $label = 'WARN'): void
{
$this->labeledLine($label, "{$message}>", 'fg=black;bg=red');
}
public function warnCommandFailed($command): void
{
$this->warn("Failed to run {$command}");
}
public function showOutputErrors(string $errors)
{
parent::text([
'--------------------------------------------------------------------------------',
str_replace(PHP_EOL, PHP_EOL . ' ', trim($errors)),
'-------------------------------------------------------------------------------->',
]);
}
public function showOutput(string $errors)
{
parent::text([
'--------------------------------------------------------------------------------',
str_replace(PHP_EOL, PHP_EOL . ' ', trim($errors)),
'--------------------------------------------------------------------------------',
]);
}
public function exception($message)
{
parent::block($message, null, 'fg=black;bg=red', ' ', true, false);
}
public function text($message)
{
parent::text($message);
}
public function listing(array $items): void
{
parent::newLine();
$text = collect($items)->map(function ($dependency) {
return ' - ' . $dependency;
})->toArray();
parent::text($text);
parent::newLine();
}
public function table(array $columnHeadings, array $rowData)
{
parent::table($columnHeadings, $rowData);
}
public function consoleOutput(string $line, $type)
{
if (config('lambo.store.with_output')) {
($type === Process::ERR)
? $this->labeledLine('!️', '┃ ' . $line, 'fg=yellow')
: $this->labeledLine('✓︎', '┃ ' . $line, 'fg=green;');
}
}
public function labeledLine(string $label, string $message, string $labelFormat = 'fg=default;bg=default', int $indentColumns = 0): void
{
$indent = str_repeat(' ', $indentColumns);
$this->isDecorated()
? parent::text("{$indent}<{$labelFormat}> {$label} > {$message}")
: parent::text("{$indent}[ {$label} ] {$message}");
}
}
================================================
FILE: app/Environment.php
================================================
getID();
}
}
================================================
FILE: app/LamboException.php
================================================
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);
}
}
================================================
FILE: app/Options.php
================================================
'e',
'long' => 'editor',
'param_description' => 'EDITOR',
'cli_description' => "Specify an editor to run 'EDITOR .' with after",
],
[
'short' => 'p',
'long' => 'path',
'param_description' => 'PATH',
'cli_description' => 'Customize the path in which the new project will be created',
],
[
'short' => 'm',
'long' => 'message',
'param_description' => 'MESSAGE',
'cli_description' => 'Customize the initial commit message (wrap with quotes!)',
],
[
'short' => 'g',
'long' => 'github',
'cli_description' => 'Initialize a new private GitHub repository',
],
[
'long' => 'gh-public',
'cli_description' => 'Make the new GitHub repository public',
],
[
'long' => 'gh-description',
'param_description' => 'DESCRIPTION',
'cli_description' => 'Initialize the new GitHub repository with the provided DESCRIPTION',
],
[
'long' => 'gh-homepage',
'param_description' => 'URL',
'cli_description' => 'Initialize the new GitHub repository with the provided homepage URL',
],
[
'long' => 'gh-org',
'param_description' => 'ORG',
'cli_description' => 'Initialize the new GitHub repository for ORG/project',
],
[
'long' => 'breeze',
'param_description' => 'STACK',
'cli_description' => 'Use the Laravel Breeze starter kit. STACK may be either blade, vue or react.',
],
[
'long' => 'jetstream',
'param_description' => 'STACK[,teams]',
'cli_description' => 'Use the Laravel Jetstream starter kit. STACK may be either inertia or livewire.' ,
],
[
'short' => 'b',
'long' => 'browser',
'param_description' => 'BROWSER',
'cli_description' => 'Open the site in the specified BROWSER. E.g. firefox',
],
[
'long' => 'dbhost',
'param_description' => 'HOST',
'cli_description' => 'Specify the database HOST',
],
[
'long' => 'dbport',
'param_description' => 'PORT',
'cli_description' => 'Specify the database PORT',
],
[
'long' => 'dbname',
'param_description' => 'NAME',
'cli_description' => 'Specify the database NAME',
],
[
'long' => 'dbuser',
'param_description' => 'USERNAME',
'cli_description' => 'Specify the database USERNAME',
],
[
'long' => 'dbpassword',
'param_description' => 'PASSWORD',
'cli_description' => 'Specify the database PASSWORD',
],
[
'long' => 'create-db',
'cli_description' => 'Create a new MySQL database',
],
[
'short' => 'f',
'long' => 'force',
'cli_description' => 'Force install even if the directory already exists',
],
[
'long' => 'migrate-db',
'cli_description' => 'Run database migrations',
],
[
'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' => 'd',
'long' => 'dev',
'cli_description' => 'Install Laravel using the develop branch',
],
[
'long' => 'full',
'cli_description' => 'Shortcut of --create-db --migrate-db --link --secure',
],
[
'short' => 'q',
'long' => 'quiet',
'cli_description' => 'Do not output to the console (except for user input)',
],
];
public function all(): array
{
return $this->options;
}
}
================================================
FILE: app/Providers/AppServiceProvider.php
================================================
set([
'home_dir' => $path,
]);
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}
================================================
FILE: app/Shell.php
================================================
rootPath = $config->get('lambo.store.root_path');
$this->projectPath = $config->get('lambo.store.project_path');
$this->consoleWriter = $consoleWriter;
}
public function execInRoot($command)
{
return $this->exec("cd {$this->rootPath} && $command");
}
public function execInProject($command)
{
return $this->exec("cd {$this->projectPath} && $command");
}
public function execIn(string $directory, string $command)
{
return $this->exec("cd {$directory} && $command");
}
public function exec(string $command)
{
$this->consoleWriter->exec($command);
$process = Process::fromShellCommandline($command)
->setTty($this->useTTY)
->setTimeout(null)
->enableOutput();
$process->run(function ($type, $buffer) {
if (empty(trim($buffer)) || $buffer === PHP_EOL) {
return;
}
foreach (explode(PHP_EOL, trim($buffer)) as $line) {
$this->consoleWriter->consoleOutput($line, $type);
}
});
$this->useTTY = false;
return $process;
}
public function execQuietly(string $command)
{
$process = Process::fromShellCommandline($command)
->setTimeout(null)
->enableOutput();
$process->run();
return $process;
}
public function withTTY()
{
$this->useTTY = true;
return $this;
}
}
================================================
FILE: app/Tools/Database.php
================================================
dsn = "{$type}:host={$host};port={$port}";
$this->username = $username;
$this->password = $password;
return $this;
}
public function fillFromUrl(string $url): self
{
$url = parse_url($url);
return $this->fill($url['scheme'], $url['host'], $url['port'], $url['user'], $url['pass']);
}
public function fillFromLamboStore(array $store): self
{
return $this->fill(
$type = 'mysql',
$host = $store['database_host'],
$port = $store['database_port'],
$username = $store['database_username'],
$password = $store['database_password']
);
}
public function ensureExists(string $databaseName = null)
{
$dsn = is_null($databaseName)
? $this->dsn
: "{$this->dsn};dbname={$databaseName}";
new PDO($dsn, $this->username, $this->password);
}
public function create(string $databaseName)
{
$connection = new PDO($this->dsn, $this->username, $this->password);
return $connection->exec("CREATE DATABASE IF NOT EXISTS {$databaseName};") === 1;
}
}
================================================
FILE: bin/testLambo.sh
================================================
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# THIS SCRIPT WILL ONLY WORK ON *NIX SYSTEMS. SORRY WINDOWS USERS.
#
# Manual testing script that speeds things up by copying a "template"
# installation of Laravel rather than running the Laravel Installer every time.
# Useful when running lambo over and over during testing.
#
# NOTE: on first run since boot the script will install a fresh version of the
# Laravel template.
#
# 1. Comment out the following in app/Commands/NewCommand:
# app(VerifyPathAvailable::class)()
# app(InstallLaravel::class)()
#
# 2. run this script passing any regular lambo flags and/or options while
# omitting new, Example:
#
# /path/to/testLambo.sh my-project [other lambo flags]
# ------------------------------------------------------------------------------
DEBUG=false
if [ "$#" -lt "1" ]; then
echo "usage: $0 name [regular lambo parameters]"
exit
fi
START_DIR=$(pwd)
cd $(dirname $0)
SCRIPT_PATH=$(pwd)
NAME=$1
shift
TEST_DIR="/tmp/lambo"
TEMPLATE_NAME="template"
PROJECT_PATH="$TEST_DIR/$NAME"
TEMPLATE_PATH="$TEST_DIR/$TEMPLATE_NAME"
if [ "$DEBUG" = true ]; then
echo " [INFO] Start directory: $START_DIR"
echo " [INFO] Script path: $SCRIPT_PATH"
echo " [INFO] Test directory: $TEST_DIR"
echo " [INFO] Project name: $NAME"
echo " [INFO] Project path: $PROJECT_PATH"
echo " [INFO] Template name: $TEMPLATE_NAME"
echo " [INFO] Template path: $TEMPLATE_PATH"
fi
# Create test directory
if [ ! -d "$TEST_DIR" ]; then
echo "*[WARN] Test directory '$TEST_DIR' does not exist, creating it now…"
mkdir $TEST_DIR
else
echo " [INFO] Using test directory '$TEST_DIR'"
fi
# Create template Laravel installation
if [ ! -d "$TEMPLATE_PATH" ]; then
echo "*[WARN] Laravel template '$TEMPLATE_PATH' does not exist, creating it now…"
cd $TEST_DIR
composer create-project laravel/laravel $TEMPLATE_NAME --remove-vcs --prefer-dist --quiet
cd $START_DIR
echo " [INFO] Created template '$TEMPLATE_PATH'"
else
echo " [INFO] Using template '$TEMPLATE_PATH'"
fi
# remove previous run.
if [ -f "$TEST_DIR/.last-run" ]; then
last_run=$(cat $TEST_DIR/.last-run)
rm -rf $last_run
echo " [INFO] Deleted previous run '$last_run'"
fi
cp -r $TEMPLATE_PATH $PROJECT_PATH
echo " [INFO] Copied laravel template '$TEMPLATE_PATH' to '$PROJECT_PATH'"
cd $TEST_DIR
echo $PROJECT_PATH > .last-run
cd $SCRIPT_PATH
echo " [INFO] Running Lambo…"
echo
echo "lambo new $NAME --path $TEST_DIR $*"
../lambo new $NAME --path $TEST_DIR $*
================================================
FILE: bootstrap/app.php
================================================
singleton(
Illuminate\Contracts\Console\Kernel::class,
LaravelZero\Framework\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
Illuminate\Foundation\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;
================================================
FILE: box.json
================================================
{
"chmod": "0755",
"directories": [
"app",
"bootstrap",
"config",
"stubs",
"vendor"
],
"files": [
"composer.json"
],
"exclude-dev-files": false,
"exclude-composer-files": false,
"compression": "GZ",
"compactors": [
"KevinGH\\Box\\Compactor\\Php",
"KevinGH\\Box\\Compactor\\Json"
]
}
================================================
FILE: composer.json
================================================
{
"name": "tightenco/lambo",
"description": "Super-powered 'laravel new' with Laravel and Valet.",
"keywords": [
"laravel",
"zonda",
"wwdhhd",
"lambo"
],
"type": "project",
"license": "MIT",
"authors": [
{
"name": "Matt Stauffer",
"email": "matt@tighten.co"
},
{
"name": "Jon Sugar"
}
],
"require": {
"php": "^8.0",
"ext-json": "*",
"ext-pdo": "*",
"ext-intl": "*"
},
"require-dev": {
"laravel-zero/framework": "^10.0",
"mockery/mockery": "^1.0",
"pestphp/pest": "^2.5",
"spatie/laravel-ray": "^1.17",
"tightenco/duster": "^1.0"
},
"autoload": {
"psr-4": {
"App\\": "app/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"config": {
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"pestphp/pest-plugin": true
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"bin": ["builds/lambo"]
}
================================================
FILE: config/app.php
================================================
'Lambo',
/*
|--------------------------------------------------------------------------
| Application Version
|--------------------------------------------------------------------------
|
| This value determines the "version" your application is currently running
| in. You may want to follow the "Semantic Versioning" - Given a version
| number MAJOR.MINOR.PATCH when an update happens: https://semver.org.
|
*/
'version' => app('git.version'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services your application utilizes. Should be true in production.
|
*/
'env' => 'development',
/*
|--------------------------------------------------------------------------
| Timezone
|--------------------------------------------------------------------------
|
| This value determines the timezone that Lambo is currently running in.
| It will be the value of "date.timezone" in php.ini or the value set
| by your operating system. If php cannot determine the value set
| by your operating system, then UTC will be used. You may set
| this value manually to use a timezone of your choosing.
*/
'timezone' => app(GetTimezone::class)(),
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
App\Providers\AppServiceProvider::class,
],
];
================================================
FILE: config/commands.php
================================================
App\Commands\HelpCommand::class,
/*
|--------------------------------------------------------------------------
| Commands Paths
|--------------------------------------------------------------------------
|
| This value determines the "paths" that should be loaded by the console's
| kernel. Foreach "path" present on the array provided below the kernel
| will extract all "Illuminate\Console\Command" based class commands.
|
*/
'paths' => [app_path('Commands')],
/*
|--------------------------------------------------------------------------
| Added Commands
|--------------------------------------------------------------------------
|
| You may want to include a single command class without having to load an
| entire folder. Here you can specify which commands should be added to
| your list of commands. The console's kernel will try to load them.
|
*/
'add' => [
// ..
],
/*
|--------------------------------------------------------------------------
| Hidden Commands
|--------------------------------------------------------------------------
|
| Your application commands will always be visible on the application list
| of commands. But you can still make them "hidden" specifying an array
| of commands below. All "hidden" commands can still be run/executed.
|
*/
'hidden' => [
NunoMaduro\LaravelConsoleSummary\SummaryCommand::class,
Symfony\Component\Console\Command\HelpCommand::class,
Illuminate\Console\Scheduling\ScheduleRunCommand::class,
Illuminate\Console\Scheduling\ScheduleFinishCommand::class,
Illuminate\Foundation\Console\VendorPublishCommand::class,
],
/*
|--------------------------------------------------------------------------
| Removed Commands
|--------------------------------------------------------------------------
|
| Do you have a service provider that loads a list of commands that
| you don't need? No problem. Laravel Zero allows you to specify
| below a list of commands that you don't to see in your app.
|
*/
'remove' => [
Symfony\Component\Console\Command\HelpCommand::class,
],
];
================================================
FILE: lambo
================================================
#!/usr/bin/env php
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,
$app->make('Symfony\Component\Console\Output\ConsoleOutput')
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status);
================================================
FILE: phpunit.xml.dist
================================================
./tests/Feature
./tests/Unit
./app
================================================
FILE: readme.md
================================================

[](https://github.com/tighten/lambo/actions?query=workflow%3A%22Run+Tests%22)
**Super-powered `laravel new` for Laravel and Valet**
Lambo is a command-line tool that replaces the Laravel installer and wraps up the most common tasks you might take when creating a Laravel app: opening it in your editor and your browser, initialize a git repository, tweak your `.env` and `.env.example`, and more.
# Requirements
- PHP 7.3+
- (optional, but beneficial) [Laravel Valet](https://laravel.com/docs/valet)
# Installation
```bash
composer global require tightenco/lambo:^3.0
```
# Upgrading
```bash
composer global update tightenco/lambo
```
# Usage
Make sure `~/.composer/vendor/bin` is in your terminal's path.
```bash
cd ~/Sites
lambo new myNextProject
```
# What exactly does it do?
- `laravel new $PROJECTNAME`
- Initialize a git repo, add all the files, and, after some changes below, make a commit with the text "Initial commit."
- Replace the `.env` (and `.env.example`) database credentials with the default macOS MySQL credentials: database of `$PROJECTNAME`, user `root`, and empty password
- Replace the `.env` (and `.env.example`) `APP_URL` with `$PROJECTNAME.$YOURVALETTLD`
- Generate an app key
- Open the project in your favorite editor
- Open `$PROJECTNAME.$YOURVALETTLD` in your browser
> Note: If your `$PROJECTNAME` has dashes (`-`) in it, they will be replaced with underscores (`_`) in the database name.
There are also a few optional behaviors based on the parameters you pass (or define in your config file), including creating a database, migrating, installing Jetstream, running Valet Link and/or Secure, and running a custom bash script of your definition after the fact.
# Customizing Lambo
While the default actions Lambo provides are great, most users will want to customize at least a few of the steps. Thankfully, Lambo is built to be customized!
There are three ways to customize your usage of Lambo: command-line arguments, a config file, and an "after" file.
Most users will want to set their preferred configuration options once and then never think about it again. That's best solved by creating a config file.
But if you find yourself needing to change the way you interact with Lambo on a project-by-project basis, you may also want to use the command-line parameters to customize Lambo when you're using it.
## Creating a config file
You can create a config file at `~/.lambo/config` rather than pass the same arguments each time you create a new project.
The following command creates the file, if it doesn't exist, and edits it:
```bash
lambo edit-config
```
The config file contains the configuration parameters you can customize, and will be read on every usage of Lambo.
## Creating an "after" file
You can also create an after file at `~/.lambo/after` to run additional commands after you create a new project.
The following command creates the file, if it doesn't exist, and edits it:
```bash
lambo edit-after
```
The after file is interpreted as a bash script, so you can include any commands here, such as installing additional composer dependencies...
```bash
# Install additional composer dependencies as you would from the command line.
echo "Installing Composer Dependencies"
composer require tightenco/mailthief tightenco/quicksand
```
...or copying additional files to your new project.
```bash
# To copy standard files to new lambo project place them in ~/.lambo/includes directory.
echo "Copying Include Files"
cp -R ~/.lambo/includes/ $PROJECTPATH
```
You also have access to variables from your config file such as `$PROJECTPATH` and `$CODEEDITOR`.
## Using command-line parameters
Any command-line parameters passed in will override Lambo's defaults and your config settings. See a [full list of the parameters you can pass in](#parameters).
# Lambo Commands
- `help` or `help-screen` show the help screen
- `edit-config` edits your config file (and creates one if it doesn't exist)
```bash
lambo edit-config
```
- `edit-after` edits your "after" file (and creates one if it doesn't exist)
```bash
lambo edit-after
```
# Configurable parameters
You can optionally pass one or more of these parameters every time you use Lambo. If you find yourself wanting to configure any of these settings every time you run Lambo, that's a perfect use for the [config files](#config-files).
- `-e` or `--editor` to define your editor command. Whatever is passed here will be run as `$EDITOR .` after creating the project.
```bash
# runs "subl ." in the project directory after creating the project
lambo new superApplication --editor=subl
```
- `-p` or `--path` to specify where to install the application.
```bash
lambo new superApplication --path=~/Sites
```
- `-m` or `--message` to set the first Git commit message.
```bash
lambo new superApplication --message="This lambo runs fast!"
```
- `-f` or `--force` to force install even if the directory already exists
```bash
# Creates a new Laravel application after deleting ~/Sites/superApplication
lambo new superApplication --force
```
- `-d` or `--dev` to choose the `develop` branch instead of `master`, getting the beta install.
```bash
lambo new superApplication --dev
```
- `-b` or `--browser` to define which browser you want to open the project in.
```bash
lambo new superApplication --browser="/Applications/Google Chrome Canary.app"
```
- `-l` or `--link` to create a Valet link to the project directory.
```bash
lambo new superApplication --link
```
- `-s` or `--secure` to secure the Valet site using https.
```bash
lambo new superApplication --secure
```
- `--create-db` to create a new MySQL database which has the same name as your project.
This requires `mysql` command to be available on your system.
```bash
lambo new superApplication --create-db
```
- `--migrate-db` to migrate your database.
```bash
lambo new superApplication --migrate-db
```
- `--dbuser` to specify the database username.
```bash
lambo new superApplication --dbuser=USER
```
- `--dbpassword` specify the database password.
```bash
lambo new superApplication --dbpassword=SECRET
```
- `--dbhost` specify the database host.
```bash
lambo new superApplication --dbhost=127.0.0.1
```
- `--breeze=STACK` to use the Laravel Breeze starter kit. `STACK` may be either `blade`, `vue` or `react`.
```bash
lambo new superApplication --breeze=blade
lambo new superApplication --breeze=vue
lambo new superApplication --breeze=react
```
- `--jetstream=STACK[,teams]` to use the Laravel Jetstream starter kit. `STACK` may be either `inertia` or `livewire`.
```bash
lambo new superApplication --jetstream=inertia
lambo new superApplication --jetstream=inertia,teams
lambo new superApplication --jetstream=livewire
lambo new superApplication --jetstream=livewire,teams
```
- `--full` to use `--create-db`, `--migrate-db`, `--link`, and `-secure`.
```bash
lambo new superApplication --full
**GitHub Repository Creation**
**Important:** To create new repositories Lambo requires one of the following tools to be installed:
- the official [GitHub command line tool](https://github.com/cli/cli#installation)
- the [hub command line tool](https://github.com/github/hub#installation)
Lambo will give you the option to continue without GitHub repository creation if neither tool is installed.
- `-g` or `--github` to Initialize a new private GitHub repository and push your new project to it.
```bash
# Repository created at https://github.com//superApplication
lambo new superApplication --github
```
- Use `--gh-public` with `--github` to make the new GitHub repository public.
```bash
lambo new superApplication --github --gh-public
```
- Use `--gh-description` with `--github` to initialize the new GitHub repository with a description.
```bash
lambo new superApplication --github --gh-description='My super application'
```
- Use `--gh-homepage` with `--github` to initialize the new GitHub repository with a homepage url.
```bash
lambo new superApplication --github --gh-homepage=https://example.com
```
- Use `--gh-org` with `--github` to initialize the new GitHub repository with a specified organization.
```bash
# Repository created at https://github.com/acme/superApplication
lambo new superApplication --github --gh-org=acme
```
-----
# For contributors:
## Process for release
If you're working with us and are assigned to push a release, here's the easiest process:
1. Visit the [Lambo Releases page](https://github.com/tighten/lambo/releases); figure out what your next tag will be (increase the third number if it's a patch or fix; increase the second number if it's adding features)
2. On your local machine, pull down the latest version of `main` (`git checkout main && git pull`)
3. Build for the version you're targeting (`./lambo app:build`)
4. Run the build once to make sure it works (`./builds/lambo`)
5. Commit your build and push it up
6. [Draft a new release](https://github.com/tighten/lambo/releases/new) with both the tag version and release title of your tag (e.g. `v1.5.1`)
7. Set the body to be a bullet-point list with simple descriptions for each of the PRs merged, as well as the PR link in parentheses at the end. For example:
`- Add a superpower (#92)`
8. Hit `Publish release`
9. Profit
## Notes for future development
- All new configuration keys must be added to the `$newConfiguration` property in `UpgradeSavedConfiguration`
- All removed or deprecated configuration keys must be added to the `$removedConfigurationKeys` property in `UpgradeSavedConfiguration`
- Any time configuration keys are changed, the `$configurationVersion` property in `UpgradeSavedConfiguration` needs to be incremented
================================================
FILE: stubs/after
================================================
#!/usr/bin/env bash
# Install additional composer dependencies as you would from the command line.
# echo "
# Installing Composer Dependencies
# "
# composer require tightenco/mailthief tightenco/quicksand
# To copy standard files to new lambo project place them in ~/.lambo/includes directory.
# echo "
# Copying Include Files
# "
# cp -R ~/.lambo/includes/ $PROJECTPATH
# To add a git commit after given modifications
# echo "
# Committing after modifications to Git
# "
# git add .
# git commit -am "Initialize Composer dependencies and additional files."
================================================
FILE: stubs/config
================================================
PROJECTPATH=
MESSAGE="Initial commit."
DEVELOP=false
CODEEDITOR=
BROWSER=
LINK=false
SECURE=false
CREATE_DATABASE=false
MIGRATE_DATABASE=false
DB_HOST=127.0.0.1
DB_PORT=
DB_NAME=
DB_USERNAME=root
DB_PASSWORD=
================================================
FILE: tests/CreatesApplication.php
================================================
make(Kernel::class)->bootstrap();
return $app;
}
}
================================================
FILE: tests/Feature/CreateDatabaseTest.php
================================================
database = $this->mock(Database::class);
}
/** @test */
function it_creates_a_mysql_database()
{
$fakeStore = [
'create_database' => true,
'database_host' => 'example.test',
'database_port' => 3306,
'database_username' => 'user',
'database_password' => 'password',
'database_name' => 'foo',
];
$this->database->shouldReceive('fillFromLamboStore')
->with($fakeStore)
->once()
->andReturnSelf();
$this->database->shouldReceive('create')
->once()
->globally()
->andReturnTrue()
->ordered();
config(['lambo.store' => $fakeStore]);
app(CreateDatabase::class)();
}
/** @test */
function it_skips_database_creation()
{
$spy = $this->spy(Database::class);
config(['lambo.store.create_database' => false]);
config(['lambo.store.database_host' => 'example.test']);
config(['lambo.store.database_port' => 3306]);
config(['lambo.store.database_username' => 'user']);
config(['lambo.store.database_password' => 'password']);
config(['lambo.store.database_name' => 'foo']);
app(CreateDatabase::class)();
$spy->shouldNotHaveReceived('find');
$spy->shouldNotHaveReceived('createSchema');
}
}
================================================
FILE: tests/Feature/CustomizeDotEnvTest.php
================================================
'my-project']);
config(['lambo.store.database_name' => 'my_project']);
config(['lambo.store.project_url' => 'http://my-project.example.com']);
config(['lambo.store.database_username' => 'username']);
config(['lambo.store.database_password' => 'password']);
config(['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);
app(CustomizeDotEnv::class)();
}
/** @test */
function it_replaces_static_strings()
{
config()->set('lambo.store.database_username', 'root');
$customizeDotEnv = app(CustomizeDotEnv::class);
$contents = 'DB_USERNAME=previous';
$contents = $customizeDotEnv->customize($contents);
$this->assertEquals('DB_USERNAME=root', $contents);
}
/** @test */
function un_targeted_lines_are_unchanged()
{
config()->set('lambo.store.database_username', 'root');
$customizeDotEnv = app(CustomizeDotEnv::class);
$contents = "DB_USERNAME=previous\nDONT_TOUCH_ME=cant_touch_me";
$contents = $customizeDotEnv->customize($contents);
$this->assertEquals("DB_USERNAME=root\nDONT_TOUCH_ME=cant_touch_me", $contents);
}
/** @test */
function lines_with_no_equals_are_unchanged()
{
$customizeDotEnv = app(CustomizeDotEnv::class);
$contents = "SOME_VALUE=previous\nABCDEFGNOEQUALS";
$contents = $customizeDotEnv->customize($contents);
$this->assertEquals("SOME_VALUE=previous\nABCDEFGNOEQUALS", $contents);
}
/** @test */
function line_breaks_remain()
{
$customizeDotEnv = app(CustomizeDotEnv::class);
$contents = "A=B\n\nC=D";
$contents = $customizeDotEnv->customize($contents);
$this->assertEquals("A=B\n\nC=D", $contents);
}
}
================================================
FILE: tests/Feature/EditConfigFileTest.php
================================================
fileName = 'my-config-file-name';
$homeDirectory = '/my/home/directory';
$this->configDirectory = "{$homeDirectory}/.lambo";
$this->configFilePath = "{$this->configDirectory}/{$this->fileName}";
config(['home_dir' => $homeDirectory]);
config(['lambo.store.editor' => 'vim']);
$this->fileTemplate = 'my-config-file-template';
}
/** @test */
function it_creates_the_config_directory_and_file_then_opens_the_file_for_editing()
{
$when = $and = $then = $this;
$when->the_config_directory_does_not_exist();
$and->the_config_directory_is_created();
$and->the_config_file_does_not_exist();
$and->the_config_file_is_created();
$then->the_config_file_is_opened_for_editing();
app(EditConfigFile::class)($this->fileName);
}
/** @test */
function it_creates_a_config_file_then_opens_the_file_for_editing()
{
$this->configDirectoryExists();
$this->configFileExists(false);
$this->successfullyCreateConfigFile();
$this->successfullyOpenInEditor();
app(EditConfigFile::class)($this->fileName);
}
/** @test */
function it_opens_a_config_file_for_editing()
{
$this->configDirectoryExists();
$this->configFileExists();
$this->successfullyOpenInEditor();
app(EditConfigFile::class)($this->fileName);
}
/** @test */
function it_throws_an_exception_if_the_configured_editor_fails_to_open()
{
$this->configDirectoryExists();
$this->configFileExists();
$this->successfullyOpenInEditor(false);
$this->expectException(LamboException::class);
app(EditConfigFile::class)($this->fileName);
}
/** @test */
function failing_to_create_the_configuration_directory_throws_an_exception()
{
$this->configDirectoryExists(false);
$this->successfullyCreateConfigDirectory(false);
$this->expectException(LamboException::class);
app(EditConfigFile::class)($this->fileName);
}
/** @test */
public function failing_to_create_the_configuration_file_throws_an_exception()
{
$this->configDirectoryExists();
$this->configFileExists(false);
$this->successfullyCreateConfigFile(false);
$this->expectException(LamboException::class);
app(EditConfigFile::class)($this->fileName);
}
private function configDirectoryExists(bool $exists = true): void
{
File::shouldReceive('isDirectory')
->with($this->configDirectory)
->once()
->andReturn($exists)
->globally()
->ordered();
}
private function successfullyCreateConfigDirectory(bool $success = true): void
{
File::shouldReceive('makeDirectory')
->with($this->configDirectory)
->once()
->andReturn($success)
->globally()
->ordered();
}
private function configFileExists(bool $success = true): void
{
File::shouldReceive('isFile')
->with($this->configFilePath)
->once()
->andReturn($success)
->globally()
->ordered();
}
private function successfullyCreateConfigFile(bool $success = true): void
{
File::shouldReceive('get')->with(base_path("stubs/{$this->fileName }"))
->once()
->andReturn($this->fileTemplate)
->globally()
->ordered();
File::shouldReceive('put')
->with($this->configFilePath, $this->fileTemplate)
->once()
->andReturn($success)
->globally()
->ordered();
}
private function successfullyOpenInEditor(bool $success = true)
{
$this->shell->shouldReceive('withTTY')
->once()
->globally()
->andReturnSelf()
->ordered();
$command = "vim {$this->fileName}";
$expectation = $this->shell->shouldReceive('execIn')
->with($this->configDirectory, $command)
->once();
if ($success) {
$expectation->andReturn(FakeProcess::success());
} else {
$expectation->andReturn(FakeProcess::fail($command));
}
return $expectation->globally()->ordered();
}
private function the_config_directory_does_not_exist()
{
File::shouldReceive('isDirectory')
->with($this->configDirectory)
->once()
->andReturn(false)
->globally()
->ordered();
}
private function the_config_directory_is_created()
{
File::shouldReceive('makeDirectory')
->with($this->configDirectory)
->once()
->andReturn(true)
->globally()
->ordered();
}
private function the_config_file_does_not_exist()
{
File::shouldReceive('isFile')
->with($this->configFilePath)
->once()
->andReturn(false)
->globally()
->ordered();
}
private function the_config_file_is_created()
{
File::shouldReceive('get')->with(base_path("stubs/{$this->fileName }"))
->once()
->andReturn($this->fileTemplate)
->globally()
->ordered();
File::shouldReceive('put')
->with($this->configFilePath, $this->fileTemplate)
->once()
->andReturn(true)
->globally()
->ordered();
}
private function the_config_file_is_opened_for_editing()
{
$this->shell->shouldReceive('withTTY')
->once()
->globally()
->andReturnSelf()
->ordered();
$this->shell->shouldReceive('execIn')
->with($this->configDirectory, "vim {$this->fileName}")
->once()
->andReturn(FakeProcess::success())
->globally()
->ordered();
}
}
================================================
FILE: tests/Feature/Fakes/FakeProcess.php
================================================
isSuccessful = $isSuccessful;
$this->failedCommand = $failedCommand;
}
public static function success(): FakeProcess
{
return new self(true);
}
public static function fail(string $failedCommand): FakeProcess
{
return new self(false, $failedCommand);
}
public function isSuccessful(): bool
{
return $this->isSuccessful;
}
public function getCommandLine(): string
{
return $this->failedCommand;
}
public function withOutput(string $output): FakeProcess
{
$this->output = $output;
return $this;
}
public function withErrorOutput(string $errorOutput): FakeProcess
{
$this->errorOutput = $errorOutput;
return $this;
}
public function getOutput()
{
return $this->output;
}
public function getErrorOutput()
{
return $this->errorOutput;
}
public function getExitCode()
{
return $this->isSuccessful ? 0 : 1;
}
}
================================================
FILE: tests/Feature/Fixtures/.lambo/commented_configuration
================================================
CODEEDITOR=vim
#QUIET=true
#AUTH=false
#NODE=
# ------------------------------------------------------------------------------
# 1-APR-2020 5:00 am (auto-generated by Lambo):
# Lambo has commented out the configuration items QUIET, AUTH and NODE;
# they are no longer used, and you may safely remove them.
# ------------------------------------------------------------------------------
================================================
FILE: tests/Feature/Fixtures/.lambo/config
================================================
CONFIGURATION_OPTION=foo
#MISSING_CONFIGURATION_OPTION=foo
CONFIGURATION_OPTION_NO_VALUE
CONFIGURATION_OPTION_EMPTY_VALUE=
BOOLEAN_OPTION_1=1
BOOLEAN_OPTION_TRUE=true
BOOLEAN_OPTION_ON=on
BOOLEAN_OPTION_YES=yes
BOOLEAN_OPTION_0=0
BOOLEAN_OPTION_FALSE=false
BOOLEAN_OPTION_OFF=off
BOOLEAN_OPTION_NO=no
================================================
FILE: tests/Feature/Fixtures/.lambo/old_configuration
================================================
CODEEDITOR=vim
QUIET=true
AUTH=false
NODE=
================================================
FILE: tests/Feature/Fixtures/composer-with-laravel-jetstream.json
================================================
{
"require": {
"laravel/jetstream": "^1.0"
}
}
================================================
FILE: tests/Feature/Fixtures/composer-without-laravel-jetstream.json
================================================
{
"require": {
}
}
================================================
FILE: tests/Feature/Fixtures/composer.json
================================================
{
"require": {
"laravel/jetstream": "^1.0"
}
}
================================================
FILE: tests/Feature/Fixtures/package-silent.json
================================================
{"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"}}
================================================
FILE: tests/Feature/Fixtures/package.json
================================================
{
"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"
}
}
================================================
FILE: tests/Feature/GenerateAppKeyTest.php
================================================
shell->shouldReceive('execInProject')
->with('php artisan key:generate --quiet')
->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 --quiet')
->once()
->andReturn(FakeProcess::fail('php artisan key:generate'));
$this->expectException(LamboException::class);
app(GenerateAppKey::class)();
}
}
================================================
FILE: tests/Feature/InitializeGitHubRepositoryTest.php
================================================
true, 'hub' => true],
['gh' => true, 'hub' => false],
['gh' => false, 'hub' => true],
['gh' => false, 'hub' => false],
];
protected $gitHubConfigurations = [
[
LamboConfiguration::GITHUB_PUBLIC => false,
LamboConfiguration::GITHUB_DESCRIPTION => null,
LamboConfiguration::GITHUB_HOMEPAGE => null,
LamboConfiguration::GITHUB_ORGANIZATION => null,
],
[
LamboConfiguration::GITHUB_PUBLIC => false,
LamboConfiguration::GITHUB_DESCRIPTION => null,
LamboConfiguration::GITHUB_HOMEPAGE => null,
LamboConfiguration::GITHUB_ORGANIZATION => 'org',
],
[
LamboConfiguration::GITHUB_PUBLIC => false,
LamboConfiguration::GITHUB_DESCRIPTION => null,
LamboConfiguration::GITHUB_HOMEPAGE => 'https://example.com',
LamboConfiguration::GITHUB_ORGANIZATION => null,
],
[
LamboConfiguration::GITHUB_PUBLIC => false,
LamboConfiguration::GITHUB_DESCRIPTION => null,
LamboConfiguration::GITHUB_HOMEPAGE => 'https://example.com',
LamboConfiguration::GITHUB_ORGANIZATION => 'org',
],
[
LamboConfiguration::GITHUB_PUBLIC => false,
LamboConfiguration::GITHUB_DESCRIPTION => 'My awesome project',
LamboConfiguration::GITHUB_HOMEPAGE => null,
LamboConfiguration::GITHUB_ORGANIZATION => null,
],
[
LamboConfiguration::GITHUB_PUBLIC => false,
LamboConfiguration::GITHUB_DESCRIPTION => 'My awesome project',
LamboConfiguration::GITHUB_HOMEPAGE => null,
LamboConfiguration::GITHUB_ORGANIZATION => 'org',
],
[
LamboConfiguration::GITHUB_PUBLIC => false,
LamboConfiguration::GITHUB_DESCRIPTION => 'My awesome project',
LamboConfiguration::GITHUB_HOMEPAGE => 'https://example.com',
LamboConfiguration::GITHUB_ORGANIZATION => null,
],
[
LamboConfiguration::GITHUB_PUBLIC => false,
LamboConfiguration::GITHUB_DESCRIPTION => 'My awesome project',
LamboConfiguration::GITHUB_HOMEPAGE => 'https://example.com',
LamboConfiguration::GITHUB_ORGANIZATION => 'org',
],
[
LamboConfiguration::GITHUB_PUBLIC => true,
LamboConfiguration::GITHUB_DESCRIPTION => null,
LamboConfiguration::GITHUB_HOMEPAGE => null,
LamboConfiguration::GITHUB_ORGANIZATION => null,
],
[
LamboConfiguration::GITHUB_PUBLIC => true,
LamboConfiguration::GITHUB_DESCRIPTION => null,
LamboConfiguration::GITHUB_HOMEPAGE => null,
LamboConfiguration::GITHUB_ORGANIZATION => 'org',
],
[
LamboConfiguration::GITHUB_PUBLIC => true,
LamboConfiguration::GITHUB_DESCRIPTION => null,
LamboConfiguration::GITHUB_HOMEPAGE => 'https://example.com',
LamboConfiguration::GITHUB_ORGANIZATION => null,
],
[
LamboConfiguration::GITHUB_PUBLIC => true,
LamboConfiguration::GITHUB_DESCRIPTION => null,
LamboConfiguration::GITHUB_HOMEPAGE => 'https://example.com',
LamboConfiguration::GITHUB_ORGANIZATION => 'org',
],
[
LamboConfiguration::GITHUB_PUBLIC => true,
LamboConfiguration::GITHUB_DESCRIPTION => 'My awesome project',
LamboConfiguration::GITHUB_HOMEPAGE => null,
LamboConfiguration::GITHUB_ORGANIZATION => null,
],
[
LamboConfiguration::GITHUB_PUBLIC => true,
LamboConfiguration::GITHUB_DESCRIPTION => 'My awesome project',
LamboConfiguration::GITHUB_HOMEPAGE => null,
LamboConfiguration::GITHUB_ORGANIZATION => 'org',
],
[
LamboConfiguration::GITHUB_PUBLIC => true,
LamboConfiguration::GITHUB_DESCRIPTION => 'My awesome project',
LamboConfiguration::GITHUB_HOMEPAGE => 'https://example.com',
LamboConfiguration::GITHUB_ORGANIZATION => null,
],
[
LamboConfiguration::GITHUB_PUBLIC => true,
LamboConfiguration::GITHUB_DESCRIPTION => 'My awesome project',
LamboConfiguration::GITHUB_HOMEPAGE => 'https://example.com',
LamboConfiguration::GITHUB_ORGANIZATION => 'org',
],
];
/** @test */
function it_manages_new_repository_initialization()
{
foreach ([true, false] as $initializeGitHub) {
foreach ($this->toolConfigurations as $toolConfiguration) {
foreach ($this->gitHubConfigurations as $gitHubConfiguration) {
config(['lambo.store.project_name' => 'name']);
config(['lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB => $initializeGitHub]);
config(['lambo.store.push_to_github' => false]);
config(['lambo.store.tools' => $toolConfiguration]);
config(['lambo.store' => array_merge(config('lambo.store'), $gitHubConfiguration)]);
if ($this->shouldCreateRepository()) {
$this->shell->shouldReceive('execInProject', [$this->getGitHubCreateCommand()])
->andReturn(FakeProcess::success());
}
if (! $this->gitHubToolingInstalled()) {
$this->expectException(LamboException::class);
}
app(InitializeGitHubRepository::class)();
if ($this->shouldCreateRepository()) {
$this->assertTrue(config('lambo.store.push_to_github'));
}
}
}
}
}
/** @test */
function it_warns_the_user_if_repository_creation_fails()
{
$consoleWriter = $this->mock(ConsoleWriter::class);
$consoleWriter->shouldReceive('logStep');
config(['lambo.store.project_name' => 'name']);
config(['lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB => true]);
config(['lambo.store.push_to_github' => false]);
config(['lambo.store.tools.gh' => true]);
$failedCommandOutput = 'Failed command output';
$this->shell->shouldReceive('execInProject')
->with($this->getGitHubCreateCommand())
->once()
->andReturn(FakeProcess::fail($this->getGitHubCreateCommand())->withErrorOutput($failedCommandOutput));
$consoleWriter->shouldReceive('warn')
->with(InitializeGitHubRepository::WARNING_FAILED_TO_CREATE_REPOSITORY)
->globally()
->ordered();
$consoleWriter->shouldReceive('warnCommandFailed')
->with($this->getGitHubCreateCommand())
->globally()
->ordered();
$consoleWriter->shouldReceive('showOutputErrors')
->with($failedCommandOutput)
->globally()
->ordered();
app(InitializeGitHubRepository::class)();
}
}
================================================
FILE: tests/Feature/InitializeGitRepositoryTest.php
================================================
'Initial commit']);
$this->shell->shouldReceive('execInProject')
->with('git init --quiet')
->once()
->andReturn(FakeProcess::success());
$this->shell->shouldReceive('execInProject')
->with('git add .')
->once()
->andReturn(FakeProcess::success());
$this->shell->shouldReceive('execInProject')
->with("git commit --quiet -m 'Initial commit'")
->once()
->andReturn(FakeProcess::success());
app(InitializeGitRepository::class)();
}
/** @test */
function it_throws_an_exception_if_git_init_fails()
{
$this->shell->shouldReceive('execInProject')
->with('git init --quiet')
->once()
->andReturn(FakeProcess::fail('git init'));
$this->expectException(LamboException::class);
app(InitializeGitRepository::class)();
}
/** @test */
function it_throws_an_exception_if_git_add_fails()
{
$this->shell->shouldReceive('execInProject')
->with('git init --quiet')
->once()
->andReturn(FakeProcess::success());
$this->shell->shouldReceive('execInProject')
->with('git add .')
->once()
->andReturn(FakeProcess::fail('git add .'));
$this->expectException(LamboException::class);
app(InitializeGitRepository::class)();
}
/** @test */
function it_throws_an_exception_if_git_commit_fails()
{
config(['lambo.store.commit_message' => 'Initial commit']);
$command = 'git init --quiet';
$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 --quiet -m 'Initial commit'")
->once()
->andReturn(FakeProcess::fail('git commit -m "Initial commit"'));
$this->expectException(LamboException::class);
app(InitializeGitRepository::class)();
}
/** @test */
function it_removes_the_quiet_flag_when_show_output_is_enabled()
{
config(['lambo.store.commit_message' => 'Initial commit']);
config(['lambo.store.with_output' => true]);
$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(InitializeGitRepository::class)();
}
}
================================================
FILE: tests/Feature/InstallBreezeTest.php
================================================
$stack]);
config(['lambo.store.with_output' => $withOutput]);
if ($this->isVerbose()) {
$this->logUseCase($stack, $withOutput);
}
$this->shouldExecInProject($this->getComposerCommand($withOutput));
$this->shouldExecInProject($this->getBreezeInstallCommand($stack, $withOutput));
$this->shouldExecInProject($this->getNpmInstallCommand($withOutput));
$this->shouldExecInProject($this->getCompileAssetsCommand($withOutput));
app(InstallBreeze::class)();
if ($this->isDebug()) {
$this->toSTDOUT("\n ✔ PASS\n");
}
}
}
}
/**
* @test
* @throws LamboException
*/
function it_skips_breeze_installation()
{
$this->spy(Shell::class);
config(['lambo.store.breeze' => false]);
app(InstallBreeze::class)();
$this->shell->shouldNotHaveReceived('execInProject');
}
/** @test */
function it_throws_a_lambo_exception_if_an_invalid_breeze_stack_is_requested()
{
config(['lambo.store.breeze' => null]);
$this->expectException(LamboException::class);
app(InstallBreeze::class)();
config(['lambo.store.breeze' => '']);
$this->expectException(LamboException::class);
app(InstallBreeze::class)();
config(['lambo.store.breeze' => 'invalid']);
$this->expectException(LamboException::class);
app(InstallBreeze::class)();
config(['lambo.store.breeze' => true]);
$this->expectException(LamboException::class);
app(InstallBreeze::class)();
}
/** @test */
function it_throws_a_lambo_exception_if_composer_installation_fails()
{
config(['lambo.store.breeze' => 'blade']);
$this->shouldExecInProjectAndFail('composer require laravel/breeze --dev --quiet');
$this->expectException(LamboException::class);
app(InstallBreeze::class)();
}
/** @test */
function it_throws_a_lambo_exception_if_breeze_installation_fails()
{
config(['lambo.store.breeze' => 'react']);
$this->shouldExecInProject('composer require laravel/breeze --dev --quiet');
$this->shouldExecInProjectAndFail('php artisan breeze:install react --quiet');
$this->expectException(LamboException::class);
app(InstallBreeze::class)();
}
private function getComposerCommand(bool $withOutput = false): string
{
return 'composer require laravel/breeze --dev' . ($withOutput ? '' : ' --quiet');
}
private function getBreezeInstallCommand(string $stack, $showOutput): string
{
return sprintf(
'php artisan breeze:install%s%s',
$stack === 'blade' ? '' : " {$stack}",
$showOutput ? '' : ' --quiet'
);
}
private function getNpmInstallCommand($showOutput): string
{
return 'npm install' . ($showOutput ? '' : ' --silent');
}
private function getCompileAssetsCommand($withOutput): string
{
return 'npm run build' . ($withOutput ? '' : ' --silent');
}
private function logUseCase(string $stack, $showOutput): void
{
$showOutputStr = $showOutput ? 'true' : 'false';
$this->toSTDOUT("────────────────────────────\n");
$this->toSTDOUT(implode(PHP_EOL, [
sprintf(' lambo new %s--breeze=%s', $showOutput ? '-v(vv) ' : '', $stack),
]), ' USE CASE');
$this->toSTDOUT(implode(PHP_EOL, [
" 1. {$this->getComposerCommand($showOutput)}",
" 2. {$this->getBreezeInstallCommand($stack, $showOutput)}",
" 3. {$this->getNpmInstallCommand($showOutput)}",
" 4. {$this->getCompileAssetsCommand($showOutput)}",
]), ' COMMAND EXECUTION ORDER');
$this->toSTDOUT(implode(PHP_EOL, [
" \$stack : {$stack}",
" \$showOutput : {$showOutputStr}",
' config(lambo.store.breeze) : ' . config('lambo.store.breeze'),
' config(lambo.store.show_output) : ' . (config('lambo.store.show_output') ? 'true' : 'false'),
]), ' TEST ITERATION CONTEXT');
}
}
================================================
FILE: tests/Feature/InstallJetstreamTest.php
================================================
$stack]);
config(['lambo.store.with_output' => $withOutput]);
if ($this->isDebug()) {
$this->logUseCase($stack, $useTeams, $withOutput);
}
$this->shouldExecInProject($this->getComposerCommand($withOutput));
$this->shouldExecInProject($this->getJetstreamInstallCommand($stack, $withOutput));
$this->shouldExecInProject($this->getNpmInstallCommand($withOutput));
$this->shouldExecInProject($this->getCompileAssetsCommand($withOutput));
app(InstallJetstream::class)();
if ($this->isDebug()) {
$this->toSTDOUT("\n ✔ PASS\n");
}
}
}
}
}
/**
* @test
* @throws LamboException
*/
function it_skips_jetstream_installation()
{
$this->spy(Shell::class);
config(['lambo.store.jetstream' => false]);
app(InstallJetstream::class)();
$this->shell->shouldNotHaveReceived('execInProject');
}
/** @test */
function it_throws_a_lambo_exception_if_an_invalid_jetstream_stack_is_requested()
{
config(['lambo.store.jetstream' => null]);
$this->expectException(LamboException::class);
app(InstallJetstream::class)();
config(['lambo.store.jetstream' => '']);
$this->expectException(LamboException::class);
app(InstallJetstream::class)();
config(['lambo.store.jetstream' => 'invalid']);
$this->expectException(LamboException::class);
app(InstallJetstream::class)();
config(['lambo.store.jetstream' => true]);
$this->expectException(LamboException::class);
app(InstallJetstream::class)();
}
/** @test */
function it_throws_a_lambo_exception_if_composer_installation_fails()
{
config(['lambo.store.jetstream' => 'inertia']);
$this->shouldExecInProjectAndFail('composer require laravel/jetstream --dev --quiet');
$this->expectException(LamboException::class);
app(InstallJetstream::class)();
}
/** @test */
function it_throws_a_lambo_exception_if_jetstream_installation_fails()
{
config(['lambo.store.jetstream' => 'inertia']);
$this->shouldExecInProject('composer require laravel/jetstream --dev --quiet');
$this->shouldExecInProjectAndFail('php artisan jetstream:install inertia --quiet');
$this->expectException(LamboException::class);
app(InstallJetstream::class)();
}
private function getJetstreamInstallCommand(string $stack, $showOutput): string
{
return "php artisan jetstream:install {$stack}" . ($showOutput ? '' : ' --quiet');
}
private function getNpmInstallCommand($showOutput): string
{
return 'npm install' . ($showOutput ? '' : ' --silent');
}
private function getComposerCommand(bool $withOutput = false): string
{
return 'composer require laravel/jetstream --dev' . ($withOutput ? '' : ' --quiet');
}
private function getCompileAssetsCommand($withOutput): string
{
return 'npm run build' . ($withOutput ? '' : ' --silent');
}
private function logUseCase(string $stack, $useTeams, $showOutput): void
{
$useTeamsStr = $useTeams ? 'true' : 'false';
$showOutputStr = $showOutput ? 'true' : 'false';
$configStack = config('lambo.store.jetstream');
$configShowOutputStr = config('lambo.store.show_output') ? 'true' : 'false';
$this->toSTDOUT("────────────────────────────\n");
$this->toSTDOUT(sprintf(
" USE CASE\n lambo new %s--jetstream=%s%s",
$showOutput ? '-v[vv] ' : '',
$stack,
$useTeams ? ',teams' : ''
));
$this->toSTDOUT(implode(PHP_EOL, [
" 1. {$this->getComposerCommand($showOutput)}",
" 2. {$this->getJetstreamInstallCommand($stack, $showOutput)}",
" 3. {$this->getNpmInstallCommand($showOutput)}",
" 4. {$this->getCompileAssetsCommand($showOutput)}",
]), ' COMMAND EXECUTION ORDER');
$this->toSTDOUT(implode(PHP_EOL, [
" \$stack : {$stack}",
" \$useTeams : {$useTeamsStr}",
" \$showOutput : {$showOutputStr}",
" config(lambo.store.jetstream) : {$configStack}",
" config(lambo.store.show_output) : {$configShowOutputStr}",
]), ' TEST ITERATION CONTEXT');
}
}
================================================
FILE: tests/Feature/InstallLaravelTest.php
================================================
false, 'lambo.store.with_output' => false],
['lambo.store.dev' => false, 'lambo.store.with_output' => true],
['lambo.store.dev' => true, 'lambo.store.with_output' => false],
['lambo.store.dev' => true, 'lambo.store.with_output' => true],
])->each(function ($options) {
config(['lambo.store.project_name' => 'my-project']);
config(['lambo.store.dev' => $options['lambo.store.dev']]);
config(['lambo.store.with_output' => $options['lambo.store.with_output']]);
$this->shell->shouldReceive('execInRoot')
->with(sprintf(
'composer create-project laravel/laravel %s%s --remove-vcs --prefer-dist %s',
config('lambo.store.project_name'),
config('lambo.store.dev') ? ' dev-master' : '',
config('lambo.store.with_output') ? '' : '--quiet'
))
->once()
->andReturn(FakeProcess::success());
app(InstallLaravel::class)();
});
}
/** @test */
function it_throws_an_exception_if_laravel_fails_to_install()
{
config(['lambo.store.project_name' => 'my-project']);
config(['lambo.store.dev' => false]);
config(['lambo.store.with_output' => false]);
$this->shell->shouldReceive('execInRoot')
->andReturn(FakeProcess::fail('failed command'));
$this->expectException(LamboException::class);
app(InstallLaravel::class)();
}
}
================================================
FILE: tests/Feature/InstallNpmDependenciesTest.php
================================================
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(['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(['lambo.store.with_output' => false]);
$this->shell->shouldReceive('execInProject')
->with('npm install --silent')
->once()
->andReturn(FakeProcess::fail('npm install --silent'));
$this->expectException(LamboException::class);
app(InstallNpmDependencies::class)();
}
}
================================================
FILE: tests/Feature/LamboTestEnvironment.php
================================================
with($valetConfig)
->andReturnTrue();
File::shouldReceive('get')
->with($valetConfig)
->andReturn(sprintf('{"tld": "%s"}', $tld));
}
}
================================================
FILE: tests/Feature/MigrateDatabaseTest.php
================================================
database = $this->mock(Database::class);
}
/** @test */
function it_migrates_the_database()
{
$fakeStore = [
'migrate_database' => true,
'database_host' => 'example.test',
'database_port' => 3306,
'database_username' => 'user',
'database_password' => 'password',
'database_name' => 'foo',
];
config(['lambo.store' => $fakeStore]);
$this->database->shouldReceive('fillFromLamboStore')
->with($fakeStore)
->once()
->andReturnSelf();
$this->database->shouldReceive('ensureExists')
->once()
->andReturnTrue();
$this->shell->shouldReceive('execInProject')
->with('php artisan migrate --quiet')
->once()
->andReturn(FakeProcess::success());
app(MigrateDatabase::class)();
}
/** @test */
function failed_migrations_do_not_halt_execution()
{
$fakeStore = [
'migrate_database' => true,
'database_host' => 'example.test',
'database_port' => 3306,
'database_username' => 'user',
'database_password' => 'password',
'database_name' => 'foo',
];
config(['lambo.store' => $fakeStore]);
$this->database->shouldReceive('fillFromLamboStore')
->with($fakeStore)
->once()
->andReturnSelf();
$this->database->shouldReceive('ensureExists')
->once()
->andReturnTrue();
$this->shell->shouldReceive('execInProject')
->with('php artisan migrate --quiet')
->once()
->andReturn(FakeProcess::fail('php artisan migrate --quiet'));
app(MigrateDatabase::class)();
}
/** @test */
function it_skips_migrations()
{
$databaseSpy = $this->spy(Database::class);
$shellSpy = $this->spy(Shell::class);
// Mock the Database->url() so that if it is called it
// returns properly.
$databaseSpy->shouldReceive('url')->andReturnSelf();
config(['lambo.store.migrate_database' => false]);
config(['lambo.store.database_host' => 'example.test']);
config(['lambo.store.database_port' => 3306]);
config(['lambo.store.database_username' => 'user']);
config(['lambo.store.database_password' => 'password']);
config(['lambo.store.database_name' => 'foo']);
app(MigrateDatabase::class)();
$databaseSpy->shouldNotHaveReceived('fillFromLamboStore');
$databaseSpy->shouldNotHaveReceived('ensureExists');
$shellSpy->shouldNotHaveReceived('execInProject');
}
}
================================================
FILE: tests/Feature/OpenInBrowserTest.php
================================================
environment = $this->mock(Environment::class);
}
/** @test */
function it_uses_the_open_command_on_mac_when_a_browser_is_specified()
{
config(['lambo.store.browser' => '/Applications/my/browser.app']);
config(['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_uses_valet_open_on_mac_when_no_browser_is_specified()
{
$this->assertEmpty(config('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(['lambo.store.browser' => '/path/to/a/browser']);
config(['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)();
}
/** @test */
function it_skips_opening_the_site()
{
$shell = $this->spy(Shell::class);
config(['lambo.store.no_browser' => false]);
app(OpenInBrowser::class);
$shell->shouldNotHaveReceived('execInProject');
}
}
================================================
FILE: tests/Feature/OpenInEditorTest.php
================================================
'my-editor']);
$this->shell->shouldReceive('withTTY')
->once()
->andReturnSelf();
$this->shell->shouldReceive('execInProject')
->with('my-editor .')
->once()
->andReturn(FakeProcess::success());
app(OpenInEditor::class)();
}
/** @test */
function it_throws_an_exception_if_it_fails_to_open_the_editor()
{
config(['lambo.store.editor' => 'my-editor']);
$this->shell->shouldReceive('withTTY')
->once()
->andReturnSelf();
$this->shell->shouldReceive('execInProject')
->with('my-editor .')
->once()
->andReturn(FakeProcess::fail('my-editor .'));
$this->expectException(LamboException::class);
app(OpenInEditor::class)();
}
}
================================================
FILE: tests/Feature/PushToGitHubTest.php
================================================
true]);
$this->shell->shouldReceive('execInProject')
->with('git rev-parse --abbrev-ref HEAD')
->once()
->andReturn(FakeProcess::success()->withOutput('main'))
->globally()
->ordered();
$this->shell->shouldReceive('execInProject')
->with('git push -u origin main')
->once()
->andReturn(FakeProcess::success());
app(PushToGitHub::class)();
}
/** @test */
function it_logs_a_warning_if_branch_name_cannot_be_determined()
{
$this->consoleWriter = $this->mock(ConsoleWriter::class);
config(['lambo.store.push_to_github' => true]);
$this->shouldLogStep('Pushing new project to GitHub');
$getBranchNameCommand = 'git rev-parse --abbrev-ref HEAD';
$errorMessage = 'Oops, something went wrong.';
$failedBranchNameProcess = FakeProcess::fail($getBranchNameCommand)
->withErrorOutput($errorMessage);
$this->shell->shouldReceive('execInProject')
->with($getBranchNameCommand)
->once()
->andReturn($failedBranchNameProcess)
->globally()
->ordered();
$this->shouldLogWarning(PushToGitHub::WARNING_UNABLE_TO_GET_BRANCH_NAME);
$this->shouldLogWarning("Failed to run {$getBranchNameCommand}");
$this->shouldShowOutputErrors($errorMessage);
app(PushToGitHub::class)();
}
/** @test */
function it_logs_a_warning_if_pushing_to_git_hub_fails()
{
$this->consoleWriter = $this->mock(ConsoleWriter::class);
config(['lambo.store.push_to_github' => true]);
$this->shouldLogStep('Pushing new project to GitHub');
$branchNameProcess = FakeProcess::success()->withOutput('main');
$this->shell->shouldReceive('execInProject')
->with('git rev-parse --abbrev-ref HEAD')
->once()
->andReturn($branchNameProcess)
->globally()
->ordered();
$pushToGitHubCommand = "git push -u origin {$branchNameProcess->getOutput()}";
$errorMessage = 'Oops, something went wrong.';
$failedPushToGitHubProcess = FakeProcess::fail($pushToGitHubCommand)
->withErrorOutput($errorMessage);
$this->shell->shouldReceive('execInProject')
->with($pushToGitHubCommand)
->once()
->andReturn($failedPushToGitHubProcess)
->globally()
->ordered();
$this->shouldLogWarning(PushToGitHub::WARNING_FAILED_TO_PUSH);
$this->shouldLogWarning("Failed to run {$pushToGitHubCommand}");
$this->shouldShowOutputErrors($errorMessage);
app(PushToGitHub::class)();
}
/** @test */
function it_skips_pushing_to_github()
{
$shell = $this->spy(Shell::class);
$pushCommand = 'git push -u origin ';
config(['lambo.store.push_to_github' => null]);
app(PushToGitHub::class)();
$shell->shouldNotHaveReceived('execInProject', [$pushCommand]);
config(['lambo.store.push_to_github' => false]);
app(PushToGitHub::class)();
$shell->shouldNotHaveReceived('execInProject', [$pushCommand]);
}
private function shouldLogWarning(string $warning): void
{
$this->consoleWriter->shouldReceive('warn')
->with($warning)
->globally()
->ordered();
}
private function shouldLogStep(string $step)
{
$this->consoleWriter->shouldReceive('logStep')
->with($step)
->globally()
->ordered();
}
private function shouldShowOutputErrors(string $error)
{
$this->consoleWriter->shouldReceive('showOutputErrors')
->with($error)
->globally()
->ordered();
}
}
================================================
FILE: tests/Feature/RunAfterScriptTest.php
================================================
$this->getHomeDirectory()]);
config(['lambo.store.project_path' => $this->getProjectPath()]);
}
/** @test */
function it_runs_the_after_script_if_one_exists()
{
File::shouldReceive('isFile')
->with($this->getAfterScriptPath())
->andReturn(true)
->globally()
->ordered();
$this->shell->shouldReceive('withTTY')
->once()
->globally()
->ordered();
$this->shell->shouldReceive('execInProject')
->with($this->getCommand())
->once()
->andReturn(FakeProcess::success())
->globally()
->ordered();
app(RunAfterScript::class)();
}
/** @test */
function it_throws_an_exception_if_the_after_script_fails()
{
File::shouldReceive('isFile')
->with($this->getAfterScriptPath())
->andReturn(true)
->globally()
->ordered();
$this->shell->shouldReceive('withTTY')
->once()
->globally()
->ordered();
$this->shell->shouldReceive('execInProject')
->with($this->getCommand())
->once()
->andReturn(FakeProcess::fail($this->getCommand()))
->globally()
->ordered();
$this->expectException(LamboException::class);
app(RunAfterScript::class)();
}
private function getAfterScriptPath(): string
{
return "{$this->getHomeDirectory()}/.lambo/after";
}
private function getHomeDirectory(): string
{
return '/my/home/dir';
}
private function getCommand(): string
{
return sprintf('env PROJECTPATH=%s sh %s', $this->getProjectPath(), $this->getAfterScriptPath());
}
private function getProjectPath(): string
{
return '/my/project/path';
}
}
================================================
FILE: tests/Feature/SignatureBuilderTest.php
================================================
buildSignatureOption([
'short' => 'e',
'long' => 'editor',
'cli_description' => '',
]);
$this->assertStringContainsString('-e|editor', $output);
}
/** @test */
function it_functions_given_only_long_code()
{
$newCommand = new NewCommand();
$output = $newCommand->buildSignatureOption([
'long' => 'editor',
'cli_description' => '',
]);
$this->assertStringContainsString('-editor', $output);
}
/** @test */
function it_sets_expectation_for_values_if_option_expects_parameters()
{
$newCommand = new NewCommand();
$output = $newCommand->buildSignatureOption([
'long' => 'editor',
'param_description' => 'a',
'cli_description' => '',
]);
$this->assertStringContainsString('-editor=', $output);
}
/** @test */
function it_does_not_set_expectation_for_values_if_option_does_not_expect_parameters()
{
$newCommand = new NewCommand();
$output = $newCommand->buildSignatureOption([
'long' => 'editor',
'cli_description' => '',
]);
$this->assertStringNotContainsString('editor=', $output);
}
/** @test */
function it_defines_description()
{
$newCommand = new NewCommand();
$output = $newCommand->buildSignatureOption([
'long' => 'editor',
'cli_description' => 'The Option Description',
]);
$this->assertStringContainsString('The Option Description', $output);
}
}
================================================
FILE: tests/Feature/UpgradeSavedConfigurationTest.php
================================================
[
'commented' => true,
'default' => 'flibble',
'description' => [
'The THING parameter enables Lambo to do a thing.',
'Valid options are foo, bar and flibble (default if not specified).',
],
],
'ANOTHER_THING' => [
'commented' => false,
'default' => 'false',
'description' => [
'The ANOTHER_THING parameter enables Lambo to do a another thing.',
'Valid options are true or false (default if not specified).',
],
],
];
/** @test */
function it_upgrades_saved_configuration()
{
$this->travelTo(Carbon::parse('01-Jan-2000 00:00:00', config('app.timezone')));
$upgradedConfiguration = app(UpgradeSavedConfiguration::class)->upgrade($this->getConfiguration(), $this->removedConfigurationKeys, $this->newConfiguration);
$commented =
'VALID_KEY=foo
ANOTHER_VALID_KEY=foo
#OLD_KEY=foo
#ANOTHER_OLD_KEY=foo
# ------------------------------------------------------------------------------
# 1-Jan-2000 12:00 am (auto-generated by Lambo):
# ------------------------------------------------------------------------------
# Lambo has commented out the following configuration items as they
# are no-longer used. You may safely remove them:
# OLD_KEY=foo
# ANOTHER_OLD_KEY=foo
# Lambo has introduced new configuration options. They have been added here
# with sensible defaults; however, you should review them.
#
# The THING parameter enables Lambo to do a thing.
# Valid options are foo, bar and flibble (default if not specified).
#THING=flibble
# The ANOTHER_THING parameter enables Lambo to do a another thing.
# Valid options are true or false (default if not specified).
ANOTHER_THING=false
';
$this->assertEquals(
$commented,
$upgradedConfiguration
);
}
private function getConfiguration(): string
{
return collect(array_merge($this->validConfigurationKeys, $this->removedConfigurationKeys))->reduce(function ($carry, $configurationKey) {
return "{$carry}{$configurationKey}=foo\n";
}, '');
}
}
================================================
FILE: tests/Feature/ValetLinkTest.php
================================================
true]);
$this->shell->shouldReceive('execInProject')
->with('valet link')
->once()
->andReturn(FakeProcess::success());
app(ValetLink::class)();
}
/** @test */
function it_throws_an_exception_if_valet_link_fails()
{
config(['lambo.store.valet_link' => true]);
$command = 'valet link';
$this->shell->shouldReceive('execInProject')
->with($command)
->once()
->andReturn(FakeProcess::fail($command));
$this->expectException(LamboException::class);
app(ValetLink::class)();
}
}
================================================
FILE: tests/Feature/ValetSecureTest.php
================================================
true]);
$this->shell->shouldReceive('execInProject')
->with('valet secure')
->once()
->andReturn(FakeProcess::success());
app(ValetSecure::class)();
}
/** @test */
function it_throws_an_exception_if_valet_secure_fails()
{
config(['lambo.store.valet_secure' => true]);
$this->shell->shouldReceive('execInProject')
->with('valet secure')
->once()
->andReturn(FakeProcess::fail('valet secure'));
$this->expectException(LamboException::class);
app(ValetSecure::class)();
}
}
================================================
FILE: tests/Feature/ValidateGitHubConfigurationTest.php
================================================
true]);
config(['lambo.store.tools.hub' => true]);
config(['lambo.store.initializeGitHub' => null]);
app(ValidateGitHubConfiguration::class)();
$this->assertFalse(config('lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB));
config(['lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB => false]);
app(ValidateGitHubConfiguration::class)();
$this->assertFalse(config('lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB));
}
/** @test */
function it_logs_a_warning_if_github_tooling_is_missing()
{
$this->consoleWriter = $this->mock(ConsoleWriter::class);
$this->console = $this->mock(Command::class);
config(['lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB => true]);
config(['lambo.store.tools.gh' => false]);
config(['lambo.store.tools.hub' => false]);
$this->shouldLogWarning();
$this->shouldLogInstructions(ValidateGitHubConfiguration::INSTRUCTIONS_GITHUB_TOOLING_MISSING);
$this->shouldAskToContinue();
app(ValidateGitHubConfiguration::class)();
$this->assertFalse(config('lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB));
}
/** @test */
function configuration_is_valid_if_hub_is_installed()
{
$this->consoleWriter = $this->mock(ConsoleWriter::class);
config(['lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB => true]);
config(['lambo.store.tools.gh' => true]);
config(['lambo.store.tools.hub' => true]);
$this->shouldLogChosenGitHubTool('hub');
app(ValidateGitHubConfiguration::class)();
$this->assertTrue(config('lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB));
}
/** @test */
function configuration_is_valid_if_gh_is_installed_and_authenticated()
{
$this->consoleWriter = $this->mock(ConsoleWriter::class);
config(['lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB => true]);
config(['lambo.store.tools.gh' => true]);
config(['lambo.store.tools.hub' => false]);
$this->shell->shouldReceive('execQuietly')
->with('gh auth status')
->andReturn(FakeProcess::success());
$this->shouldLogChosenGitHubTool('gh');
app(ValidateGitHubConfiguration::class)();
$this->assertTrue(config('lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB));
}
/** @test */
function it_logs_a_warning_if_gh_is_not_authenticated_with_github()
{
$this->consoleWriter = $this->mock(ConsoleWriter::class);
$this->console = $this->mock(Command::class);
config(['lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB => true]);
config(['lambo.store.tools.gh' => true]);
config(['lambo.store.tools.hub' => false]);
$this->shell->shouldReceive('execQuietly')
->with('gh auth status')
->andReturn(FakeProcess::fail('gh auth status'));
$this->shouldLogWarning();
$this->shouldLogInstructions(ValidateGitHubConfiguration::INSTRUCTIONS_GH_NOT_AUTHENTICATED);
$this->shouldAskToContinue();
app(ValidateGitHubConfiguration::class)();
$this->assertFalse(config('lambo.store.' . LamboConfiguration::INITIALIZE_GITHUB));
}
private function shouldLogChosenGitHubTool(string $tool): void
{
$this->consoleWriter->shouldReceive('note')
->with(sprintf(ValidateGitHubConfiguration::SELECTED_GITHUB_TOOL_MESSAGE_PATTERN, $tool))
->globally()
->ordered();
}
private function shouldLogWarning(): void
{
$this->consoleWriter->shouldReceive('warn')
->with(ValidateGitHubConfiguration::WARNING_UNABLE_TO_CREATE_REPOSITORY)
->globally()
->ordered();
}
private function shouldAskToContinue(): void
{
$this->console->shouldReceive('confirm')
->with(ValidateGitHubConfiguration::QUESTION_SHOULD_CONTINUE)
->andReturnTrue()
->globally()
->ordered();
$this->swap('console', $this->console);
}
private function shouldLogInstructions(array $instructions): void
{
$this->consoleWriter->shouldReceive('text')
->with($instructions)
->globally()
->ordered();
}
}
================================================
FILE: tests/Feature/VerifyDependenciesTest.php
================================================
executableFinder = $this->mock(ExecutableFinder::class);
}
/** @test */
function it_checks_that_dependencies_are_available()
{
foreach (['composer', 'valet', 'git', 'hub', 'gh'] as $dependency) {
$this->dependencyIsAvailable($dependency);
}
app(VerifyDependencies::class)();
$this->assertTrue(config('lambo.store.tools.gh'));
$this->assertTrue(config('lambo.store.tools.hub'));
}
/** @test */
function it_marks_optional_dependencies_as_missing()
{
foreach (['composer', 'valet', 'git'] as $dependency) {
$this->dependencyIsAvailable($dependency);
}
$this->dependencyIsMissing('gh');
$this->dependencyIsMissing('hub');
app(VerifyDependencies::class)();
$this->assertFalse(config('lambo.store.tools.gh'));
$this->assertFalse(config('lambo.store.tools.hub'));
}
/** @test */
function it_throws_a_lambo_exception_if_composer_is_missing()
{
foreach (['valet', 'git', 'hub', 'gh'] as $dependency) {
$this->dependencyIsAvailable($dependency);
}
$this->dependencyIsMissing('composer');
$this->expectException(LamboException::class);
app(VerifyDependencies::class)();
}
/** @test */
function it_throws_a_lambo_exception_if_valet_is_missing()
{
foreach (['composer', 'git', 'hub', 'gh'] as $dependency) {
$this->dependencyIsAvailable($dependency);
}
$this->dependencyIsMissing('valet');
$this->expectException(LamboException::class);
app(VerifyDependencies::class)();
}
/** @test */
function it_throws_a_lambo_exception_if_git_is_missing()
{
foreach (['composer', 'valet', 'hub', 'gh'] as $dependency) {
$this->dependencyIsAvailable($dependency);
}
$this->dependencyIsMissing('git');
$this->expectException(LamboException::class);
app(VerifyDependencies::class)();
}
private function dependencyIsAvailable(string $dependency, $isAvailable = true): void
{
$foo = $this->executableFinder
->shouldReceive('find')
->with($dependency);
$isAvailable
? $foo->andReturn("/path/to/{$dependency}")
: $foo->andReturnNull();
}
private function dependencyIsMissing(string $dependency)
{
$this->dependencyIsAvailable($dependency, false);
}
}
================================================
FILE: tests/Feature/VerifyPathAvailableTest.php
================================================
'/some/filesystem/path']);
config(['lambo.store.project_path' => '/some/filesystem/path/my-project']);
File::shouldReceive('isDirectory')
->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_a_lambo_exception_if_the_root_path_is_not_available()
{
config(['lambo.store.root_path' => '/non/existent/filesystem/path']);
File::shouldReceive('isDirectory')
->with('/non/existent/filesystem/path')
->once()
->andReturn(false);
$this->expectException(LamboException::class);
app(VerifyPathAvailable::class)();
}
/** @test */
function it_throws_a_lambo_exception_if_the_project_path_already_exists()
{
config(['lambo.store.root_path' => '/some/filesystem/path']);
config(['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(LamboException::class);
app(VerifyPathAvailable::class)();
}
/** @test */
function it_ignores_a_pre_existing_directory_if_the_force_option_is_specified()
{
config(['lambo.store.root_path' => '/some/filesystem/path']);
config(['lambo.store.project_path' => '/some/filesystem/path/existing-directory']);
config(['lambo.store.force_create' => true]);
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();
File::shouldReceive('deleteDirectory')
->with('/some/filesystem/path/existing-directory')
->once()
->andReturn(true)
->globally()
->ordered();
app(VerifyPathAvailable::class)();
}
/** @test */
public function it_throws_a_lambo_exception_if_it_fails_to_delete_the_pre_existing_directory()
{
config(['lambo.store.root_path' => '/some/filesystem/path']);
config(['lambo.store.project_path' => '/some/filesystem/path/existing-directory']);
config(['lambo.store.force_create' => true]);
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();
File::shouldReceive('deleteDirectory')
->with('/some/filesystem/path/existing-directory')
->once()
->andReturn(false)
->globally()
->ordered();
$this->expectException(LamboException::class);
app(VerifyPathAvailable::class)();
}
/** @test */
function it_throws_an_exception_if_project_path_is_empty()
{
config(['lambo.store.root_path' => '/some/filesystem/path']);
config(['lambo.store.project_path' => '']);
File::shouldReceive('isDirectory')
->with('/some/filesystem/path')
->once()
->andReturn(true)
->globally()
->ordered();
$this->expectException(Exception::class);
app(VerifyPathAvailable::class)();
}
/** @test */
function it_throws_an_exception_if_project_path_is_null()
{
config(['lambo.store.root_path' => '/some/filesystem/path']);
config(['lambo.store.project_path' => null]);
File::shouldReceive('isDirectory')
->with('/some/filesystem/path')
->once()
->andReturn(true)
->globally()
->ordered();
$this->expectException(Exception::class);
app(VerifyPathAvailable::class)();
}
}
================================================
FILE: tests/TestCase.php
================================================
mockConsoleWriter();
$this->shell = $this->mock(Shell::class);
}
protected function mockConsoleWriter(): void
{
$consoleWriter = $this->mock(ConsoleWriter::class, function ($consoleWriter) {
$consoleWriter->shouldReceive('logStep');
$consoleWriter->shouldReceive('title');
$consoleWriter->shouldReceive('success');
$consoleWriter->shouldReceive('note');
$consoleWriter->shouldReceive('text');
$consoleWriter->shouldReceive('warn');
$consoleWriter->shouldReceive('fail');
$consoleWriter->shouldReceive('newLine');
});
$this->swap('console-writer', $consoleWriter);
$this->swap(ConsoleWriter::class, $consoleWriter);
}
protected function todo(array $lines)
{
$this->skipWithMessage($lines, 'TODO');
}
protected function toSTDOUT($out, string $title = null): void
{
$message = sprintf("%s%s\n", $title ? "{$title}\n" : '', print_r($out, true));
fwrite(STDOUT, $message);
}
protected function shouldExecInProject(string $command, bool $success = true)
{
$shell = $this->shell->shouldReceive('execInProject')
->with($command)
->once()
->globally()
->ordered();
$success
? $shell->andReturn(FakeProcess::success())
: $shell->andReturn(FakeProcess::fail($command));
}
protected function shouldExecInProjectAndFail(string $command)
{
$this->shouldExecInProject($command, false);
}
protected function isVerbose(): bool
{
return $this->isDebug() || in_array('--verbose', $_SERVER['argv'], true);
}
protected function isDebug(): bool
{
return in_array('--debug', $_SERVER['argv'], true);
}
}
================================================
FILE: tests/Unit/CommandLineConfigurationTest.php
================================================
mockConsole = $this->mock(Command::class);
$this->withCommandLineArgument('projectName', 'foo');
}
/** @test */
function it_gets_a_command_line_configuration_value()
{
$this->withoutCommandLineArguments();
// lambo --command-line-option=foo
$this->withCommandLineOptions(['command-line-option' => 'foo']);
$this->swap('console', $this->mockConsole);
$commandLineConfiguration = new CommandLineConfiguration([
'command-line-option' => 'genericOption',
]);
$this->assertEquals('foo', $commandLineConfiguration->genericOption);
}
/** @test */
function it_returns_null_if_a_command_line_configuration_value_is_missing()
{
$this->withoutCommandLineArguments();
$this->withoutCommandLineOptions();
$this->swap('console', $this->mockConsole);
$commandLineConfiguration = new CommandLineConfiguration([
'command-line-option' => 'genericOption',
]);
$this->assertNull($commandLineConfiguration->genericOption);
}
/** @test */
function it_returns_null_if_a_command_line_configuration_value_is_empty()
{
$this->withoutCommandLineArguments();
// lambo --command-line-option=
$this->withCommandLineOptions(['command-line-option' => '']);
$this->swap('console', $this->mockConsole);
$commandLineConfiguration = new CommandLineConfiguration([
'command-line-option' => 'genericOption',
]);
$this->assertNull($commandLineConfiguration->genericOption);
}
/** @test */
function it_returns_null_if_a_non_existent_property_is_requested()
{
$this->withoutCommandLineArguments();
$this->withoutCommandLineOptions();
$this->swap('console', $this->mockConsole);
$commandLineConfiguration = new CommandLineConfiguration([]);
$this->assertNull($commandLineConfiguration->foo);
}
protected function withoutEnvironmentVariable(array $keys): void
{
Arr::forget($_SERVER, $keys);
}
protected function withoutCommandLineOptions(): void
{
$this->withCommandLineOptions([]);
}
protected function withCommandLineOptions(array $commandLineOptions): void
{
$this->mockConsole->shouldReceive('options')
->andReturn($commandLineOptions);
}
protected function withCommandLineArgument(string $key, string $value): void
{
$this->mockConsole->shouldReceive('argument')
->with($key)
->andReturn($value);
}
private function withoutCommandLineArguments()
{
$this->mockConsole->shouldReceive('arguments')->andReturn([]);
}
}
================================================
FILE: tests/Unit/Fakes/FakeInput.php
================================================
input = $input;
$this->parameterOptions = collect($input)->mapWithKeys(function ($value, $key) {
return ["--{$key}" => $value];
})->toArray();
}
public function getFirstArgument(): null|string
{
throw new Exception('getFirstArgument() has not been implemented.');
}
public function hasParameterOption(array|string $values, bool $onlyParams = false): bool
{
return Arr::has($this->parameterOptions, (array) $values);
}
public function getParameterOption($values, $default = false, bool $onlyParams = false)
{
throw new Exception('getParameterOption() has not been implemented.');
}
public function bind(InputDefinition $definition)
{
throw new Exception('bind() has not been implemented.');
}
public function validate()
{
throw new Exception('validate() has not been implemented.');
}
public function getArguments(): array
{
throw new Exception('getArguments() has not been implemented.');
}
public function getArgument(string $name)
{
throw new Exception('getArgument() has not been implemented.');
}
public function setArgument(string $name, $value)
{
throw new Exception('setArgument() has not been implemented.');
}
public function hasArgument(string $name): bool
{
throw new Exception('hasArgument() has not been implemented.');
}
public function getOptions(): array
{
return $this->input;
}
public function getOption(string $name)
{
throw new Exception('getOption() has not been implemented.');
}
public function setOption(string $name, $value)
{
throw new Exception('setOption() has not been implemented.');
}
public function hasOption(string $name): bool
{
throw new Exception('hasOption() has not been implemented.');
}
public function isInteractive(): bool
{
throw new Exception('isInteractive() has not been implemented.');
}
public function setInteractive(bool $interactive)
{
throw new Exception('setInteractive() has not been implemented.');
}
}
================================================
FILE: tests/Unit/GetTimezoneTest.php
================================================
getId()]
);
$expectedTimezone = array_pop($availableTimezones);
ini_set('date.timezone', $expectedTimezone);
$this->assertEquals($expectedTimezone, app(GetTimezone::class)());
}
}
================================================
FILE: tests/Unit/SavedConfigurationTest.php
================================================
base_path('tests/Feature/Fixtures')]);
config(['config_dir' => '.lambo']);
}
/** @test */
function it_gets_a_saved_configuration_value()
{
$savedConfiguration = new SavedConfiguration([
'CONFIGURATION_OPTION' => 'genericOption',
]);
$this->assertEquals('foo', $savedConfiguration->genericOption);
}
/** @test */
function it_returns_null_if_a_saved_configuration_option_is_missing()
{
$savedConfiguration = new SavedConfiguration([
'MISSING_CONFIGURATION_OPTION' => 'genericOption',
]);
$this->assertNull($savedConfiguration->genericOption);
}
/** @test */
function it_returns_null_if_a_saved_configuration_option_has_no_value()
{
$savedConfiguration = new SavedConfiguration([
'CONFIGURATION_OPTION_NO_VALUE' => 'genericOption',
]);
$this->assertNull($savedConfiguration->genericOption);
}
/** @test */
function it_returns_null_if_a_saved_configuration_option_is_empty()
{
$savedConfiguration = new SavedConfiguration([
'CONFIGURATION_OPTION_EMPTY_VALUE' => 'genericOption',
]);
$this->assertNull($savedConfiguration->genericOption);
}
/** test */
function it_returns_null_when_the_configuration_file_does_not_exist()
{
config(['config_file' => 'non-existent-configuration_file']);
$savedConfiguration = new SavedConfiguration([
'CONFIGURATION_VALUE' => 'genericOption',
]);
$this->assertNull((new SavedConfiguration())->genericOption);
}
/** @test */
function it_returns_null_if_a_non_existent_property_is_requested()
{
$savedConfiguration = new SavedConfiguration([]);
$this->assertNull($savedConfiguration->foo);
}
/** @test */
function it_casts_strings_to_booleans()
{
$savedConfiguration = new SavedConfiguration([
'BOOLEAN_OPTION_1' => 'booleanOption1',
'BOOLEAN_OPTION_TRUE' => 'booleanOptionTrue',
'BOOLEAN_OPTION_ON' => 'booleanOptionOn',
'BOOLEAN_OPTION_YES' => 'booleanOptionYes',
'BOOLEAN_OPTION_0' => 'booleanOption0',
'BOOLEAN_OPTION_FALSE' => 'booleanOptionFalse',
'BOOLEAN_OPTION_OFF' => 'booleanOptionOff',
'BOOLEAN_OPTION_NO' => 'booleanOptionNo',
]);
$this->assertTrue($savedConfiguration->booleanOption1);
$this->assertTrue($savedConfiguration->booleanOptionTrue);
$this->assertTrue($savedConfiguration->booleanOptionOn);
$this->assertTrue($savedConfiguration->booleanOptionYes);
$this->assertFalse($savedConfiguration->booleanOption0);
$this->assertFalse($savedConfiguration->booleanOptionFalse);
$this->assertFalse($savedConfiguration->booleanOptionOff);
$this->assertFalse($savedConfiguration->booleanOptionNo);
}
}
================================================
FILE: tests/Unit/SetConfigTest.php
================================================
with(config('home_dir') . '/.config/valet/config.json')
->once()
->andReturnTrue()
->globally()
->ordered();
File::shouldReceive('get')
->with(config('home_dir') . '/.config/valet/config.json')
->once()
->andReturn('{"tld": "mytld"}')
->globally()
->ordered();
(new SetConfig(
$this->mock(CommandLineConfiguration::class),
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))(['tld' => null]);
$this->assertEquals('mytld', config('lambo.store.tld'));
}
/** @test */
function it_sets_the_top_level_domain_using_legacy_valet_config()
{
File::shouldReceive('isFile')
->with(config('home_dir') . '/.config/valet/config.json')
->once()
->andReturnFalse()
->globally()
->ordered();
File::shouldReceive('isFile')
->once()
->with(config('home_dir') . '/.valet/config.json')
->andReturnTrue()
->globally()
->ordered();
File::shouldReceive('get')
->with(config('home_dir') . '/.valet/config.json')
->once()
->andReturn('{"domain": "mytld"}')
->globally()
->ordered();
(new SetConfig(
$this->mock(CommandLineConfiguration::class),
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))(['tld' => null]);
$this->assertEquals('mytld', config('lambo.store.tld'));
}
/** @test */
function it_throws_a_LamboException_if_valet_config_is_missing()
{
File::shouldReceive('isFile')
->with(config('home_dir') . '/.config/valet/config.json')
->once()
->andReturnFalse()
->globally()
->ordered();
File::shouldReceive('isFile')
->once()
->with(config('home_dir') . '/.valet/config.json')
->andReturnFalse()
->globally()
->ordered();
$this->expectException(LamboException::class);
(new SetConfig(
$this->mock(CommandLineConfiguration::class),
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))(['tld' => null]);
}
/** @test */
function it_prioritises_command_line_configuration()
{
$this->withValetTld();
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$commandLineConfiguration->testKey = 'command-line-parameter';
$savedConfiguration = $this->mock(SavedConfiguration::class);
$savedConfiguration->testKey = 'saved-config-parameter';
$shellConfiguration = $this->mock(ShellConfiguration::class);
$shellConfiguration->testKey = 'shell-environment-parameter';
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$shellConfiguration,
app(ConsoleWriter::class),
new FakeInput()
))([
'testKey' => 'default',
]);
$this->assertEquals('command-line-parameter', config('lambo.store.testKey'));
}
/** @test */
function it_prioritises_saved_configuration_over_shell_environment_configuration()
{
$this->withValetTld();
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$commandLineConfiguration->testKey = null;
$savedConfiguration = $this->mock(SavedConfiguration::class);
$savedConfiguration->testKey = 'saved-config-parameter';
$shellConfiguration = $this->mock(ShellConfiguration::class);
$shellConfiguration->testKey = 'shell-environment-parameter';
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$shellConfiguration,
app(ConsoleWriter::class),
new FakeInput()
))([
'testKey' => 'default',
]);
$this->assertEquals('saved-config-parameter', config('lambo.store.testKey'));
}
/** @test */
function it_prioritises_shell_environment_over_default_configuration()
{
$this->withValetTld();
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$commandLineConfiguration->testKey = null;
$savedConfiguration = $this->mock(SavedConfiguration::class);
$savedConfiguration->testKey = null;
$shellConfiguration = $this->mock(ShellConfiguration::class);
$shellConfiguration->testKey = 'shell-environment-parameter';
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$shellConfiguration,
app(ConsoleWriter::class),
new FakeInput()
))([
'testKey' => 'default',
]);
$this->assertEquals('shell-environment-parameter', config('lambo.store.testKey'));
}
/** @test */
function it_uses_a_default_configuration()
{
$this->withValetTld();
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$commandLineConfiguration->testKey = null;
$savedConfiguration = $this->mock(SavedConfiguration::class);
$savedConfiguration->testKey = null;
$shellConfiguration = $this->mock(ShellConfiguration::class);
$shellConfiguration->testKey = null;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$shellConfiguration,
app(ConsoleWriter::class),
new FakeInput()
))([
'testKey' => 'default',
]);
$this->assertEquals('default', config('lambo.store.testKey'));
}
/** @test */
function it_replaces_tilda_in_root_path()
{
config(['home_dir' => '/home/user']);
$this->withValetTld();
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$commandLineConfiguration->root_path = '~/path/from/command/line';
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))(['root_path' => getcwd()]);
$this->assertEquals('/home/user/path/from/command/line', config('lambo.store.root_path'));
config(['lambo.store' => []]);
$savedConfiguration = $this->mock(SavedConfiguration::class);
$savedConfiguration->root_path = '~/path/from/saved/configuration';
(new SetConfig(
$this->mock(CommandLineConfiguration::class),
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))(['root_path' => getcwd()]);
$this->assertEquals('/home/user/path/from/saved/configuration', config('lambo.store.root_path'));
}
/** @test */
function it_replaces_hyphens_with_underscores_in_database_names()
{
$this->withValetTld();
config(['home_dir' => '/home/user']);
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$commandLineConfiguration->project_name = 'foo';
$commandLineConfiguration->database_name = 'h-y-p-h-e-n-s';
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'root_path' => getcwd(),
'project_name' => null,
'database_name' => null,
]);
$this->assertEquals('h_y_p_h_e_n_s', config('lambo.store.database_name'));
}
/** @test */
function it_sets_the_project_url()
{
$this->withValetTld('test-domain');
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$commandLineConfiguration->project_name = 'foo';
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'command' => NewCommand::class,
'tld' => null,
'project_name' => null,
'root_path' => '/some/path',
'valet_secure' => false,
]);
$this->assertEquals('http://foo.test-domain', config('lambo.store.project_url'));
config(['lambo.store' => []]);
$commandLineConfiguration->valet_secure = true;
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'command' => NewCommand::class,
'tld' => null,
'project_name' => null,
'root_path' => '/some/path',
'valet_secure' => false,
]);
$this->assertEquals('https://foo.test-domain', config('lambo.store.project_url'));
}
/** @test */
function it_sets_the_project_name()
{
$this->withValetTld();
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$commandLineConfiguration->project_name = 'foo';
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))(['project_name' => null]);
$this->assertEquals('foo', config('lambo.store.project_name'));
}
/** @test */
function it_sets_the_project_path()
{
$this->withValetTld();
config(['home_dir' => '/home/user']);
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$commandLineConfiguration->root_path = '/path/from/command/line';
$commandLineConfiguration->project_name = 'my-project';
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'command' => NewCommand::class,
'root_path' => getcwd(),
'project_name' => null,
]);
$this->assertEquals('/path/from/command/line/my-project', config('lambo.store.project_path'));
}
/** @test */
function it_sets_the_create_database_configuration()
{
$this->withValetTld();
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$savedConfiguration = $this->mock(SavedConfiguration::class);
$commandLineConfiguration->full = true;
$commandLineConfiguration->create_database = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'create_database' => false,
]);
$this->assertTrue(config('lambo.store.create_database'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->create_database = true;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'create_database' => false,
]);
$this->assertTrue(config('lambo.store.create_database'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->create_database = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'create_database' => false,
]);
$this->assertFalse(config('lambo.store.create_database'));
}
/** @test */
function it_sets_the_migrate_database_configuration()
{
$this->withValetTld();
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$savedConfiguration = $this->mock(SavedConfiguration::class);
$commandLineConfiguration->full = true;
$commandLineConfiguration->migrate_database = false;
$commandLineConfiguration->inertia = false;
$commandLineConfiguration->livewire = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'migrate_database' => false,
'inertia' => false,
'livewire' => false,
]);
$this->assertTrue(config('lambo.store.migrate_database'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->migrate_database = true;
$commandLineConfiguration->inertia = false;
$commandLineConfiguration->livewire = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'migrate_database' => false,
'inertia' => false,
'livewire' => false,
]);
$this->assertTrue(config('lambo.store.migrate_database'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->migrate_database = false;
$commandLineConfiguration->inertia = false;
$commandLineConfiguration->livewire = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'migrate_database' => false,
'inertia' => false,
'livewire' => false,
]);
$this->assertFalse(config('lambo.store.migrate_database'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->migrate_database = false;
$commandLineConfiguration->inertia = true;
$commandLineConfiguration->livewire = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'migrate_database' => false,
'inertia' => false,
'livewire' => false,
]);
$this->assertTrue(config('lambo.store.migrate_database'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->migrate_database = false;
$commandLineConfiguration->inertia = false;
$commandLineConfiguration->livewire = true;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'migrate_database' => false,
'inertia' => false,
'livewire' => false,
]);
$this->assertTrue(config('lambo.store.migrate_database'));
}
/** @test */
function it_sets_the_valet_link_configuration()
{
$this->withValetTld('test-domain');
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$savedConfiguration = $this->mock(SavedConfiguration::class);
$commandLineConfiguration->full = true;
$commandLineConfiguration->valet_link = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'valet_link' => false,
]);
$this->assertTrue(config('lambo.store.valet_link'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->valet_link = true;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'valet_link' => false,
]);
$this->assertTrue(config('lambo.store.valet_link'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->valet_link = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'valet_link' => false,
]);
$this->assertFalse(config('lambo.store.valet_link'));
}
/** @test */
function it_sets_the_valet_secure_configuration()
{
$this->withValetTld('test-domain');
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
$savedConfiguration = $this->mock(SavedConfiguration::class);
$commandLineConfiguration->full = true;
$commandLineConfiguration->valet_secure = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => true,
'valet_secure' => false,
]);
$this->assertTrue(config('lambo.store.valet_secure'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->valet_secure = true;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'valet_secure' => true,
]);
$this->assertTrue(config('lambo.store.valet_secure'));
config(['lambo.store' => []]);
$commandLineConfiguration->full = false;
$commandLineConfiguration->valet_secure = false;
(new SetConfig(
$commandLineConfiguration,
$savedConfiguration,
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput()
))([
'tld' => null,
'full' => false,
'valet_secure' => false,
]);
$this->assertFalse(config('lambo.store.valet_secure'));
}
/**
* @test
* @group front-end-scaffolding
*/
function it_sets_the_breeze_starter_kit_configuration()
{
$this->withValetTld('test-domain');
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
foreach (InstallBreeze::VALID_STACKS as $stack) {
config(['lambo.store' => []]);
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput([
'breeze' => $stack,
])
))([
'breeze' => false,
]);
static::assertEquals($stack, config('lambo.store.breeze'));
static::assertFalse(config('lambo.store.jetstream'));
if (in_array('--debug', $_SERVER['argv'], true)) {
$this->toSTDOUT(sprintf('[ Options ] -jetstream=%s', $stack));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.jetstream => %s', config('lambo.store.jetstream') ? config('lambo.store.jetstream') : 'false'));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.breeze => %s', config('lambo.store.breeze') ? config('lambo.store.breeze') : 'false'));
$this->toSTDOUT('-------------');
}
}
}
/**
* @test
* @group front-end-scaffolding
*/
function it_sets_the_jetstream_starter_kit_configuration()
{
$this->withValetTld('test-domain');
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
foreach (InstallJetstream::VALID_CONFIGURATIONS as $stack) {
config(['lambo.store' => []]);
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
app(ConsoleWriter::class),
new FakeInput([
'jetstream' => $stack,
])
))([
'jetstream' => false,
]);
static::assertEquals($stack, config('lambo.store.jetstream'));
static::assertFalse(config('lambo.store.breeze'));
if (in_array('--debug', $_SERVER['argv'], true)) {
$this->toSTDOUT(sprintf('[ Options ] -jetstream=%s', $stack));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.jetstream => %s', config('lambo.store.jetstream') ? config('lambo.store.jetstream') : 'false'));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.breeze => %s', config('lambo.store.breeze') ? config('lambo.store.breeze') : 'false'));
$this->toSTDOUT('-------------');
}
}
}
/**
* @test
* @group front-end-scaffolding
*/
function it_ensures_only_one_starter_kit_is_configured()
{
$this->withValetTld('test-domain');
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
foreach (InstallBreeze::VALID_STACKS as $breezeStack) {
foreach (InstallJetstream::VALID_CONFIGURATIONS as $jetstreamStack) {
foreach (['None', 'Laravel Breeze', 'Laravel Jetstream',] as $stackChoice) {
$consoleWriter = $this->mock(ConsoleWriter::class, function (MockInterface $consoleWriter) use ($stackChoice) {
$consoleWriter->shouldReceive('newLine')->once()->globally()->ordered();
$consoleWriter->shouldReceive('note')->once()->globally()->ordered();
$consoleWriter->shouldReceive('choice')->once()->globally()->ordered()->andReturn($stackChoice);
$stackChoice === 'None'
? $consoleWriter->shouldReceive('ok')->with('Skipping starter-kit installation.')->once()->globally()->ordered()
: $consoleWriter->shouldReceive('ok')->with("Using {$stackChoice}")->once()->globally()->ordered();
});
config(['lambo.store' => []]);
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
$consoleWriter,
new FakeInput([
'jetstream' => $jetstreamStack,
'breeze' => $breezeStack,
])
))([
'breeze' => false,
'jetstream' => false,
]);
switch ($stackChoice) {
case 'Laravel Breeze':
static::assertEquals($breezeStack, config('lambo.store.breeze'));
static::assertFalse(config('lambo.store.jetstream'));
break;
case 'Laravel Jetstream':
static::assertEquals($jetstreamStack, config('lambo.store.jetstream'));
static::assertFalse(config('lambo.store.breeze'));
break;
case 'None':
static::assertFalse(config('lambo.store.breeze'));
static::assertFalse(config('lambo.store.jetstream'));
break;
}
if (in_array('--debug', $_SERVER['argv'], true)) {
$this->toSTDOUT(sprintf('[ Options ] --breeze=%s --jetstream=%s', $breezeStack, $jetstreamStack));
$this->toSTDOUT(sprintf('[ Choice ] %s', $stackChoice));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.jetstream => %s', config('lambo.store.jetstream') ? config('lambo.store.jetstream') : 'false'));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.breeze => %s', config('lambo.store.breeze') ? config('lambo.store.breeze') : 'false'));
$this->toSTDOUT('-------------');
}
}
}
}
}
/**
* @test
* @group front-end-scaffolding
*/
function it_asks_for_clarification_when_breeze_configuration_is_invalid()
{
$this->withValetTld('test-domain');
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
foreach ([null, '', 'invalid'] as $invalidStack) {
foreach (array_keys(InstallBreeze::VALID_STACKS) as $stackChoice) {
$consoleWriter = $this->mock(ConsoleWriter::class, function (MockInterface $consoleWriter) use ($stackChoice) {
$consoleWriter->shouldReceive('note')->once()->globally()->ordered();
$consoleWriter->shouldReceive('choice')->once()->globally()->ordered()
->with(Mockery::type('string'), array_keys(InstallBreeze::VALID_STACKS))
->andReturn($stackChoice);
$consoleWriter->shouldReceive('ok')->once()->globally()->ordered();
});
config(['lambo.store' => []]);
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
$consoleWriter,
new FakeInput([
'breeze' => $invalidStack,
])
))([
'breeze' => false,
]);
static::assertEquals(Str::lower($stackChoice), config('lambo.store.breeze'));
static::assertFalse(config('lambo.store.jetstream'));
if (in_array('--debug', $_SERVER['argv'], true)) {
$this->toSTDOUT(sprintf('[ Options ] %s', is_null($invalidStack) ? '--breeze' : "--breeze={$invalidStack}"));
$this->toSTDOUT(sprintf('[ Choice ] %s', $stackChoice));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.jetstream => %s', config('lambo.store.jetstream') ? config('lambo.store.jetstream') : 'false'));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.breeze => %s', config('lambo.store.breeze') ? config('lambo.store.breeze') : 'false'));
$this->toSTDOUT('-------------');
}
}
}
}
/**
* @test
* @group front-end-scaffolding
*/
function it_asks_for_clarification_when_jetstream_configuration_is_invalid()
{
$this->withValetTld('test-domain');
$commandLineConfiguration = $this->mock(CommandLineConfiguration::class);
foreach ([null, '', 'invalid'] as $invalidStack) {
foreach (array_keys(InstallJetstream::VALID_STACKS) as $stackChoice) {
foreach ([true, false] as $useTeams) {
$consoleWriter = $this->mock(ConsoleWriter::class, function (MockInterface $consoleWriter) use ($useTeams, $stackChoice) {
$consoleWriter->shouldReceive('note')->once()->globally()->ordered();
$consoleWriter->shouldReceive('choice')->once()->globally()->ordered()
->with(Mockery::type('string'), array_keys(InstallJetstream::VALID_STACKS))
->andReturn($stackChoice);
$consoleWriter->shouldReceive('confirm')->once()->globally()->ordered()->andReturn($useTeams);
$consoleWriter->shouldReceive('ok')->once()->globally()->ordered();
});
config(['lambo.store' => []]);
(new SetConfig(
$commandLineConfiguration,
$this->mock(SavedConfiguration::class),
$this->mock(ShellConfiguration::class),
$consoleWriter,
new FakeInput([
'jetstream' => $invalidStack,
])
))([
'jetstream' => false,
]);
static::assertEquals(Str::lower($stackChoice) . ($useTeams ? ',teams' : ''), config('lambo.store.jetstream'));
static::assertFalse(config('lambo.store.breeze'));
if (in_array('--debug', $_SERVER['argv'], true)) {
$this->toSTDOUT(sprintf('[ Options ] %s', is_null($invalidStack) ? '--breeze' : "--breeze={$invalidStack}"));
$this->toSTDOUT(sprintf('[ Choice ] %s%s', Str::lower($stackChoice), $useTeams ? ' with teams' : ''));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.jetstream => %s', config('lambo.store.jetstream') ? config('lambo.store.jetstream') : 'false'));
$this->toSTDOUT(sprintf('[ Config ] lambo.store.breeze => %s', config('lambo.store.breeze') ? config('lambo.store.breeze') : 'false'));
$this->toSTDOUT('-------------');
}
}
}
}
}
}
================================================
FILE: tests/Unit/ShellConfigurationTest.php
================================================
'genericOption',
]);
$this->assertEquals('foo', $shellConfiguration->genericOption);
}
/** @test */
function it_returns_null_if_a_shell_environment_variable_is_missing()
{
Arr::forget($_SERVER, 'SHELL_ENVIRONMENT_VARIABLE');
$shellConfiguration = new ShellConfiguration([
'SHELL_ENVIRONMENT_VARIABLE' => 'genericOption',
]);
$this->assertNull($shellConfiguration->genericOption);
}
/** @test */
function it_returns_null_if_a_shell_environment_variable_is_empty()
{
Arr::set($_SERVER, 'SHELL_ENVIRONMENT_VARIABLE', '');
$shellConfiguration = new ShellConfiguration([
'SHELL_ENVIRONMENT_VARIABLE' => 'genericOption',
]);
$this->assertNull($shellConfiguration->genericOption);
}
/** @test */
function it_returns_null_if_a_non_existent_property_is_requested()
{
$shellConfiguration = new ShellConfiguration([]);
$this->assertNull($shellConfiguration->foo);
}
}
================================================
FILE: tlint.json
================================================
{
"preset": "tighten",
"disabled": [],
"excluded": [
"node_modules"
]
}