Skip to content

Commit

Permalink
Breeze "API" / SPA Stack (#109)
Browse files Browse the repository at this point in the history
This adds a new api "stack" to Breeze. This stack removes all JS and CSS related scaffolding and installs controllers that properly handle the API authentication (powered by Sanctum) that would typically be required when authenticating a JavaScript SPA such as applications powered by Next.js / Nuxt.js, etc.
  • Loading branch information
taylorotwell authored Dec 2, 2021
1 parent d320a59 commit 61ffbd4
Show file tree
Hide file tree
Showing 31 changed files with 1,241 additions and 220 deletions.
234 changes: 14 additions & 220 deletions src/Console/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@

class InstallCommand extends Command
{
use InstallsApiStack, InstallsBladeStack, InstallsInertiaStacks;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'breeze:install {stack=blade : The development stack that should be installed (blade,react,vue)}
protected $signature = 'breeze:install {stack=blade : The development stack that should be installed (blade,react,vue,api)}
{--inertia : Indicate that the Vue Inertia stack should be installed (Deprecated)}
{--pest : Indicate that Pest should be installed }
{--composer=global : Absolute path to the Composer binary which should be used to install packages}';
Expand All @@ -35,67 +37,13 @@ public function handle()
{
if ($this->option('inertia') || $this->argument('stack') === 'vue') {
return $this->installInertiaVueStack();
}

if ($this->argument('stack') === 'react') {
} elseif ($this->argument('stack') === 'react') {
return $this->installInertiaReactStack();
} elseif ($this->argument('stack') === 'api') {
return $this->installApiStack();
} else {
return $this->installBladeStack();
}

// NPM Packages...
$this->updateNodePackages(function ($packages) {
return [
'@tailwindcss/forms' => '^0.2.1',
'alpinejs' => '^3.4.2',
'autoprefixer' => '^10.1.0',
'postcss' => '^8.2.1',
'postcss-import' => '^12.0.1',
'tailwindcss' => '^2.0.2',
] + $packages;
});

// Controllers...
(new Filesystem)->ensureDirectoryExists(app_path('Http/Controllers/Auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/App/Http/Controllers/Auth', app_path('Http/Controllers/Auth'));

// Requests...
(new Filesystem)->ensureDirectoryExists(app_path('Http/Requests/Auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/App/Http/Requests/Auth', app_path('Http/Requests/Auth'));

// Views...
(new Filesystem)->ensureDirectoryExists(resource_path('views/auth'));
(new Filesystem)->ensureDirectoryExists(resource_path('views/layouts'));
(new Filesystem)->ensureDirectoryExists(resource_path('views/components'));

(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/resources/views/auth', resource_path('views/auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/resources/views/layouts', resource_path('views/layouts'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/resources/views/components', resource_path('views/components'));

copy(__DIR__.'/../../stubs/default/resources/views/dashboard.blade.php', resource_path('views/dashboard.blade.php'));

// Components...
(new Filesystem)->ensureDirectoryExists(app_path('View/Components'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/App/View/Components', app_path('View/Components'));

// Tests...
$this->installTests();

// Routes...
copy(__DIR__.'/../../stubs/default/routes/web.php', base_path('routes/web.php'));
copy(__DIR__.'/../../stubs/default/routes/auth.php', base_path('routes/auth.php'));

// "Dashboard" Route...
$this->replaceInFile('/home', '/dashboard', resource_path('views/welcome.blade.php'));
$this->replaceInFile('Home', 'Dashboard', resource_path('views/welcome.blade.php'));
$this->replaceInFile('/home', '/dashboard', app_path('Providers/RouteServiceProvider.php'));

// Tailwind / Webpack...
copy(__DIR__.'/../../stubs/default/tailwind.config.js', base_path('tailwind.config.js'));
copy(__DIR__.'/../../stubs/default/webpack.mix.js', base_path('webpack.mix.js'));
copy(__DIR__.'/../../stubs/default/resources/css/app.css', resource_path('css/app.css'));
copy(__DIR__.'/../../stubs/default/resources/js/app.js', resource_path('js/app.js'));

$this->info('Breeze scaffolding installed successfully.');
$this->comment('Please execute the "npm install && npm run dev" command to build your assets.');
}

/**
Expand All @@ -107,173 +55,19 @@ protected function installTests()
{
(new Filesystem)->ensureDirectoryExists(base_path('tests/Feature/Auth'));

$stubStack = $this->argument('stack') === 'api' ? 'api' : 'default';

if ($this->option('pest')) {
$this->requireComposerPackages('pestphp/pest:^1.16', 'pestphp/pest-plugin-laravel:^1.1');

(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/pest-tests/Feature', base_path('tests/Feature/Auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/pest-tests/Unit', base_path('tests/Unit'));
(new Filesystem)->copy(__DIR__.'/../../stubs/default/pest-tests/Pest.php', base_path('tests/Pest.php'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/'.$stubStack.'/pest-tests/Feature', base_path('tests/Feature/Auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/'.$stubStack.'/pest-tests/Unit', base_path('tests/Unit'));
(new Filesystem)->copy(__DIR__.'/../../stubs/'.$stubStack.'/pest-tests/Pest.php', base_path('tests/Pest.php'));
} else {
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/tests/Feature', base_path('tests/Feature/Auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/'.$stubStack.'/tests/Feature', base_path('tests/Feature/Auth'));
}
}

/**
* Install the Inertia Vue Breeze stack.
*
* @return void
*/
protected function installInertiaVueStack()
{
// Install Inertia...
$this->requireComposerPackages('inertiajs/inertia-laravel:^0.4.3', 'laravel/sanctum:^2.8', 'tightenco/ziggy:^1.0');

// NPM Packages...
$this->updateNodePackages(function ($packages) {
return [
'@inertiajs/inertia' => '^0.10.0',
'@inertiajs/inertia-vue3' => '^0.5.1',
'@inertiajs/progress' => '^0.2.6',
'@tailwindcss/forms' => '^0.2.1',
'@vue/compiler-sfc' => '^3.0.5',
'autoprefixer' => '^10.2.4',
'postcss' => '^8.2.13',
'postcss-import' => '^14.0.1',
'tailwindcss' => '^2.1.2',
'vue' => '^3.0.5',
'vue-loader' => '^16.1.2',
] + $packages;
});

// Controllers...
(new Filesystem)->ensureDirectoryExists(app_path('Http/Controllers/Auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia-common/app/Http/Controllers/Auth', app_path('Http/Controllers/Auth'));

// Requests...
(new Filesystem)->ensureDirectoryExists(app_path('Http/Requests/Auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/App/Http/Requests/Auth', app_path('Http/Requests/Auth'));

// Middleware...
$this->installMiddlewareAfter('SubstituteBindings::class', '\App\Http\Middleware\HandleInertiaRequests::class');

copy(__DIR__.'/../../stubs/inertia-common/app/Http/Middleware/HandleInertiaRequests.php', app_path('Http/Middleware/HandleInertiaRequests.php'));

// Views...
copy(__DIR__.'/../../stubs/inertia-common/resources/views/app.blade.php', resource_path('views/app.blade.php'));

// Components + Pages...
(new Filesystem)->ensureDirectoryExists(resource_path('js/Components'));
(new Filesystem)->ensureDirectoryExists(resource_path('js/Layouts'));
(new Filesystem)->ensureDirectoryExists(resource_path('js/Pages'));

(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia-vue/resources/js/Components', resource_path('js/Components'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia-vue/resources/js/Layouts', resource_path('js/Layouts'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia-vue/resources/js/Pages', resource_path('js/Pages'));

// Tests...
$this->installTests();

// Routes...
copy(__DIR__.'/../../stubs/inertia-common/routes/web.php', base_path('routes/web.php'));
copy(__DIR__.'/../../stubs/inertia-common/routes/auth.php', base_path('routes/auth.php'));

// "Dashboard" Route...
$this->replaceInFile('/home', '/dashboard', resource_path('js/Pages/Welcome.vue'));
$this->replaceInFile('Home', 'Dashboard', resource_path('js/Pages/Welcome.vue'));
$this->replaceInFile('/home', '/dashboard', app_path('Providers/RouteServiceProvider.php'));

// Tailwind / Webpack...
copy(__DIR__.'/../../stubs/inertia-common/tailwind.config.js', base_path('tailwind.config.js'));
copy(__DIR__.'/../../stubs/inertia-common/webpack.mix.js', base_path('webpack.mix.js'));
copy(__DIR__.'/../../stubs/inertia-common/webpack.config.js', base_path('webpack.config.js'));
copy(__DIR__.'/../../stubs/inertia-common/jsconfig.json', base_path('jsconfig.json'));
copy(__DIR__.'/../../stubs/inertia-common/resources/css/app.css', resource_path('css/app.css'));
copy(__DIR__.'/../../stubs/inertia-vue/resources/js/app.js', resource_path('js/app.js'));

$this->info('Breeze scaffolding installed successfully.');
$this->comment('Please execute the "npm install && npm run dev" command to build your assets.');
}

/**
* Install the Inertia React Breeze stack.
*
* @return void
*/
protected function installInertiaReactStack()
{
// Install Inertia...
$this->requireComposerPackages('inertiajs/inertia-laravel:^0.3.5', 'laravel/sanctum:^2.6', 'tightenco/ziggy:^1.0');

// NPM Packages...
$this->updateNodePackages(function ($packages) {
return [
'@headlessui/react' => '^1.2.0',
'@inertiajs/inertia' => '^0.9.0',
'@inertiajs/inertia-react' => '^0.6.0',
'@inertiajs/progress' => '^0.2.4',
'@tailwindcss/forms' => '^0.3.2',
'autoprefixer' => '^10.2.4',
'postcss' => '^8.2.13',
'postcss-import' => '^14.0.1',
'tailwindcss' => '^2.1.2',
'react' => '^17.0.2',
'react-dom' => '^17.0.2',
'@babel/preset-react' => '^7.13.13',
] + $packages;
});

// Controllers...
(new Filesystem)->ensureDirectoryExists(app_path('Http/Controllers/Auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia-common/app/Http/Controllers/Auth', app_path('Http/Controllers/Auth'));

// Requests...
(new Filesystem)->ensureDirectoryExists(app_path('Http/Requests/Auth'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/App/Http/Requests/Auth', app_path('Http/Requests/Auth'));

// Middleware...
$this->installMiddlewareAfter('SubstituteBindings::class', '\App\Http\Middleware\HandleInertiaRequests::class');

copy(__DIR__.'/../../stubs/inertia-common/app/Http/Middleware/HandleInertiaRequests.php', app_path('Http/Middleware/HandleInertiaRequests.php'));

// Views...
copy(__DIR__.'/../../stubs/inertia-common/resources/views/app.blade.php', resource_path('views/app.blade.php'));

// Components + Pages...
(new Filesystem)->ensureDirectoryExists(resource_path('js/Components'));
(new Filesystem)->ensureDirectoryExists(resource_path('js/Layouts'));
(new Filesystem)->ensureDirectoryExists(resource_path('js/Pages'));

(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia-react/resources/js/Components', resource_path('js/Components'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia-react/resources/js/Layouts', resource_path('js/Layouts'));
(new Filesystem)->copyDirectory(__DIR__.'/../../stubs/inertia-react/resources/js/Pages', resource_path('js/Pages'));

// Tests...
$this->installTests();

// Routes...
copy(__DIR__.'/../../stubs/inertia-common/routes/web.php', base_path('routes/web.php'));
copy(__DIR__.'/../../stubs/inertia-common/routes/auth.php', base_path('routes/auth.php'));

// "Dashboard" Route...
$this->replaceInFile('/home', '/dashboard', resource_path('js/Pages/Welcome.js'));
$this->replaceInFile('Home', 'Dashboard', resource_path('js/Pages/Welcome.js'));
$this->replaceInFile('/home', '/dashboard', app_path('Providers/RouteServiceProvider.php'));

// Tailwind / Webpack...
copy(__DIR__.'/../../stubs/inertia-common/tailwind.config.js', base_path('tailwind.config.js'));
copy(__DIR__.'/../../stubs/inertia-common/webpack.mix.js', base_path('webpack.mix.js'));
copy(__DIR__.'/../../stubs/inertia-common/webpack.config.js', base_path('webpack.config.js'));
copy(__DIR__.'/../../stubs/inertia-common/jsconfig.json', base_path('jsconfig.json'));
copy(__DIR__.'/../../stubs/inertia-common/resources/css/app.css', resource_path('css/app.css'));
copy(__DIR__.'/../../stubs/inertia-react/resources/js/app.js', resource_path('js/app.js'));

$this->replaceInFile('.vue()', '.react()', base_path('webpack.mix.js'));
$this->replaceInFile('.vue', '.js', base_path('tailwind.config.js'));

$this->info('Breeze scaffolding installed successfully.');
$this->comment('Please execute the "npm install && npm run dev" command to build your assets.');
}

/**
* Install the middleware to a group in the application Http Kernel.
*
Expand Down
97 changes: 97 additions & 0 deletions src/Console/InstallsApiStack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace Laravel\Breeze\Console;

use Illuminate\Filesystem\Filesystem;

trait InstallsApiStack
{
/**
* Install the API Breeze stack.
*
* @return void
*/
protected function installApiStack()
{
$files = new Filesystem;

// Controllers...
$files->ensureDirectoryExists(app_path('Http/Controllers/Auth'));
$files->copyDirectory(__DIR__.'/../../stubs/api/App/Http/Controllers/Auth', app_path('Http/Controllers/Auth'));

// Middleware...
$files->copyDirectory(__DIR__.'/../../stubs/api/App/Http/Middleware', app_path('Http/Middleware'));

$this->replaceInFile('// \Laravel\Sanctum\Http', '\Laravel\Sanctum\Http', app_path('Http/Kernel.php'));

$this->replaceInFile(
'\Illuminate\Auth\Middleware\EnsureEmailIsVerified::class',
'\App\Http\Middleware\EnsureEmailIsVerified::class',
app_path('Http/Kernel.php')
);

// Requests...
$files->ensureDirectoryExists(app_path('Http/Requests/Auth'));
$files->copyDirectory(__DIR__.'/../../stubs/api/App/Http/Requests/Auth', app_path('Http/Requests/Auth'));

// Providers...
$files->copyDirectory(__DIR__.'/../../stubs/api/App/Providers', app_path('Providers'));

// Routes...
copy(__DIR__.'/../../stubs/api/routes/api.php', base_path('routes/api.php'));
copy(__DIR__.'/../../stubs/api/routes/web.php', base_path('routes/web.php'));
copy(__DIR__.'/../../stubs/api/routes/auth.php', base_path('routes/auth.php'));

// Configuration...
$files->copyDirectory(__DIR__.'/../../stubs/api/config', config_path());

$this->replaceInFile(
"'url' => env('APP_URL', 'http://localhost')",
"'url' => env('APP_URL', 'http://localhost'),".PHP_EOL.PHP_EOL." 'frontend_url' => env('FRONTEND_URL', 'http://localhost:3000')",
config_path('app.php')
);

// Environment...
if (! $files->exists(base_path('.env'))) {
copy(base_path('.env.example'), base_path('.env'));
}

$this->replaceInFile(
'APP_URL=http://localhost',
'APP_URL=http://localhost'.PHP_EOL.'FRONTEND_URL=http://localhost:3000',
base_path('.env')
);

// Tests...
$this->installTests();

$files->delete(base_path('tests/Feature/Auth/PasswordConfirmationTest.php'));

// Cleaning...
$this->removeScaffoldingUnnecessaryForApis();

$this->info('Breeze scaffolding installed successfully.');
}

/**
* Remove any application scaffolding that isn't needed for APIs.
*
* @return void
*/
protected function removeScaffoldingUnnecessaryForApis()
{
$files = new Filesystem;

// Remove frontend related files...
$files->delete(base_path('package.json'));
$files->delete(base_path('webpack.mix.js'));

// Remove Laravel "welcome" view...
$files->delete(resource_path('views/welcome.blade.php'));
$files->put(resource_path('views/.gitkeep'), PHP_EOL);

// Remove CSS and JavaScript directories...
$files->deleteDirectory(resource_path('css'));
$files->deleteDirectory(resource_path('js'));
}
}
Loading

0 comments on commit 61ffbd4

Please sign in to comment.