├── .editorconfig ├── .gitignore ├── .styleci.yml ├── LICENSE.txt ├── README.md ├── composer.json ├── spark ├── spark.bat └── src ├── Installation ├── AddAppProviderToConfiguration.php ├── AddCoreProviderToConfiguration.php ├── CompileAssets.php ├── ComposerUpdate.php ├── CreateLaravelProject.php ├── DownloadSpark.php ├── RunNpmInstall.php ├── RunSparkInstall.php └── UpdateComposerFile.php ├── InteractsWithSparkAPI.php ├── InteractsWithSparkConfiguration.php ├── NewCommand.php ├── RegisterCommand.php ├── TokenCommand.php └── cacert.pem /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | js: true 4 | css: true 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Spark Installer 2 | 3 | ## Installation 4 | 5 | You should clone this repository to any location on your system, then run the `composer install` command within the cloned directory so the installer's dependencies will be installed. Finally add that location to your system's PATH so that the `spark` executable can be run from anywhere on your system. 6 | 7 | After purchasing a Spark license, run the `spark register` command with your API token generated from the [Laravel Spark](https://spark.laravel.com) website. 8 | 9 | spark register token-value 10 | 11 | ## Creating Projects 12 | 13 | Once your Spark client has been registered, you can run the `new` command to create new projects: 14 | 15 | spark new project-name 16 | 17 | After the project has been created, don't forget to run your database migrations! 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/spark-installer", 3 | "description": "Command line installer for Laravel Spark.", 4 | "keywords": ["spark", "laravel"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Taylor Otwell", 9 | "email": "taylor@laravel.com" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "Laravel\\SparkInstaller\\": "src/" 15 | } 16 | }, 17 | "require": { 18 | "illuminate/support": "^5.1|^6.0|^7.0", 19 | "symfony/console": "^2.7|^3.0|^4.0|^5.0", 20 | "symfony/process": "^2.7|^3.0|^4.0|^5.0", 21 | "illuminate/filesystem": "^5.1|^6.0|^7.0", 22 | "guzzlehttp/guzzle": "^6.0" 23 | }, 24 | "bin": [ 25 | "spark" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /spark: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new Laravel\SparkInstaller\NewCommand); 15 | $app->add(new Laravel\SparkInstaller\RegisterCommand); 16 | $app->add(new Laravel\SparkInstaller\TokenCommand); 17 | 18 | $app->run(); 19 | -------------------------------------------------------------------------------- /spark.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | setlocal DISABLEDELAYEDEXPANSION 3 | SET BIN_TARGET=%~dp0/spark 4 | php "%BIN_TARGET%" %* 5 | -------------------------------------------------------------------------------- /src/Installation/AddAppProviderToConfiguration.php: -------------------------------------------------------------------------------- 1 | command = $command; 20 | } 21 | 22 | /** 23 | * Run the installation helper. 24 | * 25 | * @return void 26 | */ 27 | public function install() 28 | { 29 | $path = $this->command->path.'/config/app.php'; 30 | 31 | $contents = file_get_contents($path); 32 | 33 | $contents = str_replace( 34 | ' App\\Providers\\AppServiceProvider::class,', 35 | " App\Providers\SparkServiceProvider::class,\n App\Providers\AppServiceProvider::class,", 36 | $contents 37 | ); 38 | 39 | $contents = str_replace( 40 | ' App\\Providers\\AppServiceProvider::class,', 41 | " Laravel\Cashier\CashierServiceProvider::class,\n App\Providers\AppServiceProvider::class,", 42 | $contents 43 | ); 44 | 45 | file_put_contents($path, $contents); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Installation/AddCoreProviderToConfiguration.php: -------------------------------------------------------------------------------- 1 | command = $command; 20 | } 21 | 22 | /** 23 | * Run the installation helper. 24 | * 25 | * @return void 26 | */ 27 | public function install() 28 | { 29 | $path = $this->command->path.'/config/app.php'; 30 | 31 | $contents = file_get_contents($path); 32 | 33 | $contents = str_replace( 34 | ' App\\Providers\\AppServiceProvider::class,', 35 | " Laravel\Spark\Providers\SparkServiceProvider::class,\n App\Providers\AppServiceProvider::class,", 36 | $contents 37 | ); 38 | 39 | file_put_contents($path, $contents); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Installation/CompileAssets.php: -------------------------------------------------------------------------------- 1 | command = $command; 21 | } 22 | 23 | /** 24 | * Run the installation helper. 25 | * 26 | * @return void 27 | */ 28 | public function install() 29 | { 30 | if (! $this->command->output->confirm('Would you like to compile your assets?', true)) { 31 | return; 32 | } 33 | 34 | $this->command->output->writeln('Running Build Script...'); 35 | 36 | $process = Process::fromShellCommandline('npm run dev', $this->command->path)->setTimeout(null); 37 | 38 | if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { 39 | $process->setTty(true); 40 | } 41 | 42 | $process->run(function ($type, $line) { 43 | $this->command->output->write($line); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Installation/ComposerUpdate.php: -------------------------------------------------------------------------------- 1 | command = $command; 21 | } 22 | 23 | /** 24 | * Run the installation helper. 25 | * 26 | * @return void 27 | */ 28 | public function install() 29 | { 30 | $process = Process::fromShellCommandline('composer update', $this->command->path)->setTimeout(null); 31 | 32 | if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { 33 | $process->setTty(true); 34 | } 35 | 36 | $process->run(function ($type, $line) { 37 | $this->command->output->write($line); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Installation/CreateLaravelProject.php: -------------------------------------------------------------------------------- 1 | name = $name; 23 | $this->command = $command; 24 | } 25 | 26 | /** 27 | * Run the installation helper. 28 | * 29 | * @return void 30 | */ 31 | public function install() 32 | { 33 | $process = Process::fromShellCommandline('laravel new '.$this->name); 34 | 35 | if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { 36 | $process->setTty(true); 37 | } 38 | 39 | $process->setTimeout(null)->run(function ($type, $line) { 40 | $this->command->output->write($line); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Installation/DownloadSpark.php: -------------------------------------------------------------------------------- 1 | command = $command; 29 | } 30 | 31 | /** 32 | * Run the installation helper. 33 | * 34 | * @return void 35 | */ 36 | public function install() 37 | { 38 | $this->extractZip($this->downloadZip()); 39 | 40 | rename($this->sparkPath(), $this->command->path.'/spark'); 41 | 42 | (new Filesystem)->deleteDirectory( 43 | $this->command->path.'/spark-new' 44 | ); 45 | } 46 | 47 | /** 48 | * Download the latest Spark release. 49 | * 50 | * @return void 51 | */ 52 | protected function downloadZip() 53 | { 54 | $this->command->output->writeln( 55 | 'Downloading Spark...' 56 | ); 57 | 58 | file_put_contents( 59 | $zipPath = $this->command->path.'/spark-archive.zip', $this->zipResponse() 60 | ); 61 | 62 | return $zipPath; 63 | } 64 | 65 | /** 66 | * Get the raw Zip response for a Spark download. 67 | * 68 | * @return string 69 | */ 70 | protected function zipResponse() 71 | { 72 | $release = $this->latestSparkRelease(); 73 | 74 | try { 75 | return (string) (new HttpClient)->get( 76 | $this->sparkUrl.'/api/releases/'.$release.'/download?api_token='.$this->readToken(), 77 | [ 78 | 'headers' => [ 79 | 'X-Requested-With' => 'XMLHttpRequest', 80 | ], 81 | 'verify' => __DIR__.'/../cacert.pem', 82 | ] 83 | )->getBody(); 84 | } catch (ClientException $e) { 85 | if ($e->getResponse()->getStatusCode() === 401) { 86 | $this->invalidLicense($release); 87 | } 88 | 89 | throw $e; 90 | } 91 | } 92 | 93 | /** 94 | * Extract the Spark Zip archive. 95 | * 96 | * @param string $zipPath 97 | * @return void 98 | */ 99 | protected function extractZip($zipPath) 100 | { 101 | $archive = new ZipArchive; 102 | 103 | $archive->open($zipPath); 104 | 105 | $archive->extractTo($this->command->path.'/spark-new'); 106 | 107 | $archive->close(); 108 | 109 | @unlink($zipPath); 110 | } 111 | 112 | /** 113 | * Get the release directory. 114 | * 115 | * @return string 116 | */ 117 | protected function sparkPath() 118 | { 119 | return $this->command->path.'/spark-new/'.basename( 120 | (new Filesystem)->directories($this->command->path.'/spark-new')[0] 121 | ); 122 | } 123 | 124 | /** 125 | * Inform the user that their registered Spark token is invalid. 126 | * 127 | * @return void 128 | */ 129 | protected function invalidLicense($release) 130 | { 131 | $this->command->output->writeln( 132 | 'You do not own any licenses for release ['.$release.'].' 133 | ); 134 | 135 | exit(1); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Installation/RunNpmInstall.php: -------------------------------------------------------------------------------- 1 | command = $command; 21 | } 22 | 23 | /** 24 | * Run the installation helper. 25 | * 26 | * @return void 27 | */ 28 | public function install() 29 | { 30 | if (! $this->command->output->confirm('Would you like to install the NPM dependencies?', true)) { 31 | return; 32 | } 33 | 34 | $this->command->output->writeln('Installing NPM Dependencies...'); 35 | 36 | $process = Process::fromShellCommandline('npm set progress=false && npm install', $this->command->path)->setTimeout(null); 37 | 38 | if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { 39 | $process->setTty(true); 40 | } 41 | 42 | $process->run(function ($type, $line) { 43 | $this->command->output->write($line); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Installation/RunSparkInstall.php: -------------------------------------------------------------------------------- 1 | command = $command; 21 | } 22 | 23 | /** 24 | * Run the installation helper. 25 | * 26 | * @return void 27 | */ 28 | public function install() 29 | { 30 | $process = Process::fromShellCommandline($this->command(), $this->command->path); 31 | 32 | if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { 33 | $process->setTty(true); 34 | } 35 | 36 | $process->run(function ($type, $line) { 37 | $this->command->output->write($line); 38 | }); 39 | } 40 | 41 | /** 42 | * Get the proper Spark installation command. 43 | * 44 | * @return string 45 | */ 46 | protected function command() 47 | { 48 | $command = 'php artisan spark:install --force'; 49 | 50 | if ($this->command->input->getOption('team-billing')) { 51 | $command .= ' --team-billing'; 52 | } 53 | 54 | return $command; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Installation/UpdateComposerFile.php: -------------------------------------------------------------------------------- 1 | name = $name; 22 | $this->command = $command; 23 | } 24 | 25 | /** 26 | * Run the installation helper. 27 | * 28 | * @return void 29 | */ 30 | public function install() 31 | { 32 | $composer = $this->getComposerConfiguration(); 33 | 34 | // Next we will add the Spark and Cashier dependencies to the Composer array as 35 | // well as add the Spark "repository" to the configuration so Composer knows 36 | // where Spark is located. Spark will get installed using the path option. 37 | $composer = $this->addRepository( 38 | $this->addSparkDependencies($composer) 39 | ); 40 | 41 | $this->writeComposerFile($composer); 42 | } 43 | 44 | /** 45 | * Read the Composer file from disk. 46 | * 47 | * @return array 48 | */ 49 | protected function getComposerConfiguration() 50 | { 51 | return json_decode(file_get_contents( 52 | $this->command->path.'/composer.json' 53 | ), true); 54 | } 55 | 56 | /** 57 | * Add the Spark Composer dependencies. 58 | * 59 | * @param array $composer 60 | * @return array 61 | */ 62 | protected function addSparkDependencies($composer) 63 | { 64 | $composer['require']['laravel/spark-aurelius'] = '*@dev'; 65 | $composer['require']['laravel/ui'] = '^2.0'; 66 | 67 | return $composer; 68 | } 69 | 70 | /** 71 | * Add the Spark repository to the Composer array. 72 | * 73 | * @param array $composer 74 | * @return array 75 | */ 76 | protected function addRepository($composer) 77 | { 78 | $composer['repositories'] = [[ 79 | 'type' => 'path', 80 | 'url' => './spark', 81 | ]]; 82 | 83 | return $composer; 84 | } 85 | 86 | /** 87 | * Write the given Composer configuration back to disk. 88 | * 89 | * @param array $composer 90 | * @return void 91 | */ 92 | protected function writeComposerFile($composer) 93 | { 94 | file_put_contents( 95 | $this->command->path.'/composer.json', 96 | json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/InteractsWithSparkAPI.php: -------------------------------------------------------------------------------- 1 | get( 24 | $this->sparkUrl.'/api/releases/version', ['verify' => __DIR__.'/cacert.pem'] 25 | )->getBody())->version; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/InteractsWithSparkConfiguration.php: -------------------------------------------------------------------------------- 1 | configExists()) { 18 | throw new Exception("Spark configuration file not found. Please register a token."); 19 | } 20 | 21 | return json_decode( 22 | file_get_contents($this->configPath()), true 23 | )['token']; 24 | } 25 | 26 | /** 27 | * Write the Spark token to the configuration. 28 | * 29 | * @param string $token 30 | * @return void 31 | */ 32 | protected function storeToken($token) 33 | { 34 | file_put_contents($this->configPath(), json_encode([ 35 | 'token' => $token 36 | ], JSON_PRETTY_PRINT).PHP_EOL); 37 | } 38 | 39 | /** 40 | * Determine if the Spark configuration file exists. 41 | * 42 | * @return bool 43 | */ 44 | protected function configExists() 45 | { 46 | return file_exists($this->configPath()); 47 | } 48 | 49 | /** 50 | * Get the Spark configuration file path. 51 | * 52 | * @return string 53 | */ 54 | protected function configPath() 55 | { 56 | return $this->homePath().'/.spark/config.json'; 57 | } 58 | 59 | /** 60 | * Get the User's home path. 61 | * 62 | * @return string 63 | * @throws Exception 64 | */ 65 | protected function homePath() 66 | { 67 | if (! empty($_SERVER['HOME'])) { 68 | return $_SERVER['HOME']; 69 | } elseif (! empty($_SERVER['HOMEDRIVE']) && ! empty($_SERVER['HOMEPATH'])) { 70 | return $_SERVER['HOMEDRIVE'].$_SERVER['HOMEPATH']; 71 | } else { 72 | throw new Exception('Cannot determine home directory.'); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/NewCommand.php: -------------------------------------------------------------------------------- 1 | setName('new') 44 | ->setDescription('Create a new Spark application') 45 | ->addArgument('name', InputArgument::REQUIRED, 'The name of the application') 46 | ->addOption('team-billing', null, InputOption::VALUE_NONE, 'Configure Spark for team based billing'); 47 | } 48 | 49 | /** 50 | * Execute the command. 51 | * 52 | * @param InputInterface $input 53 | * @param OutputInterface $output 54 | * @return integer 55 | */ 56 | public function execute(InputInterface $input, OutputInterface $output) 57 | { 58 | $this->input = $input; 59 | $this->output = new SymfonyStyle($input, $output); 60 | 61 | $this->path = getcwd().'/'.$input->getArgument('name'); 62 | 63 | $installers = [ 64 | Installation\CreateLaravelProject::class, 65 | Installation\DownloadSpark::class, 66 | Installation\UpdateComposerFile::class, 67 | Installation\ComposerUpdate::class, 68 | Installation\AddCoreProviderToConfiguration::class, 69 | Installation\RunSparkInstall::class, 70 | Installation\AddAppProviderToConfiguration::class, 71 | Installation\RunNpmInstall::class, 72 | Installation\CompileAssets::class, 73 | ]; 74 | 75 | foreach ($installers as $installer) { 76 | (new $installer($this, $input->getArgument('name')))->install(); 77 | } 78 | 79 | return 0; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/RegisterCommand.php: -------------------------------------------------------------------------------- 1 | setName('register') 26 | ->setDescription('Register an API token with the installer') 27 | ->addArgument('token', InputArgument::REQUIRED, 'The API token'); 28 | } 29 | 30 | /** 31 | * Execute the command. 32 | * 33 | * @param InputInterface $input 34 | * @param OutputInterface $output 35 | * @return integer 36 | */ 37 | public function execute(InputInterface $input, OutputInterface $output) 38 | { 39 | if (! $this->valid($input->getArgument('token'))) { 40 | return $this->tokenIsInvalid($output); 41 | } 42 | 43 | if (! $this->configExists()) { 44 | mkdir($this->homePath().'/.spark'); 45 | } 46 | 47 | $this->storeToken($input->getArgument('token')); 48 | 49 | $this->tokenIsValid($output); 50 | 51 | return 0; 52 | } 53 | 54 | /** 55 | * Determine if the given token is valid. 56 | * 57 | * @param string $token 58 | * @return bool 59 | */ 60 | protected function valid($token) 61 | { 62 | try { 63 | (new HttpClient)->get( 64 | $this->sparkUrl.'/api/token/'.$token.'/validate', 65 | ['verify' => __DIR__.'/cacert.pem'] 66 | ); 67 | 68 | return true; 69 | } catch (Exception $e) { 70 | var_dump($e->getMessage()); 71 | 72 | return false; 73 | } 74 | } 75 | 76 | /** 77 | * Inform the user that the token is valid. 78 | * 79 | * @param OutputInterface $output 80 | * @return void 81 | */ 82 | protected function tokenIsValid($output) 83 | { 84 | $output->writeln('Validating Token: '); 85 | 86 | $output->writeln('Thanks for registering Spark!'); 87 | } 88 | 89 | /** 90 | * Inform the user that the token is invalid. 91 | * 92 | * @param OutputInterface $output 93 | * @return void 94 | */ 95 | protected function tokenIsInvalid($output) 96 | { 97 | $output->writeln('Validating Token: ✘'); 98 | 99 | $output->writeln('This API token is invalid.'); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/TokenCommand.php: -------------------------------------------------------------------------------- 1 | setName('token') 22 | ->setDescription('Display the currently registered Spark API token'); 23 | } 24 | 25 | /** 26 | * Execute the command. 27 | * 28 | * @param InputInterface $input 29 | * @param OutputInterface $output 30 | * @return integer 31 | */ 32 | public function execute(InputInterface $input, OutputInterface $output) 33 | { 34 | $output->writeln('Spark API Token: '.$this->readToken()); 35 | 36 | return 0; 37 | } 38 | } 39 | --------------------------------------------------------------------------------