├── DOCUMENTATION.md ├── README.md └── addons └── Spock ├── Commander.php ├── Git.php ├── Process.php ├── RunProcesses.php ├── SpockListener.php ├── SpockServiceProvider.php ├── default.yaml ├── meta.yaml ├── resources └── lang │ └── en │ └── settings.php ├── settings.yaml └── tests ├── CommanderTest.php └── GitTest.php /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | 1. Copy the `addons/Spock` directory into `site/addons`. 4 | 2. Whitelist the environments Spock should be run in. [Read more.](#whitelisting-environments) 5 | 6 | ## Commands 7 | 8 | Out of the box, Spock will perform a few git commands to stage, commit, and push any affected files. 9 | 10 | Basically, it will do this: 11 | 12 | ``` bash 13 | # Any `commands_before` will be run here 14 | # ... 15 | 16 | # Each affected file will be staged in a separate command 17 | git add modified_file.md 18 | git add another_modified_file.md 19 | git add deleted_file.md 20 | 21 | git commit -m "Data saved by bob" # or Fieldset saved, Asset uploaded, etc... 22 | 23 | git push # this is opt-in 24 | 25 | # Any `commands_after` will be run here 26 | # ... 27 | ``` 28 | 29 | You may enable `git push`-ing in your `spock.yaml`. 30 | 31 | ``` yaml 32 | git_push: true 33 | ``` 34 | 35 | You may add hardcoded commands before or after the git commands by adding `commands_before` and/or `commands_after` to your `spock.yaml`: 36 | 37 | ``` yaml 38 | commands_before: 39 | - some-unix-command 40 | commands_after: 41 | - another-unix-command 42 | ``` 43 | 44 | ## Naming Spock's Commits 45 | 46 | By default, Spock will commit via the username and email configured in Git (which is usually you). 47 | 48 | You can override this by editing the "Git Username" and/or "Git Email" fields in the Spock addon settings area of the Control Panel, or add the variables to your `site/settings/addons/spock.yaml`, for example: 49 | 50 | ``` yaml 51 | git_username: Spock 52 | git_email: spock@domain.com 53 | ``` 54 | 55 | 56 | ## Custom commands 57 | 58 | The Git workflow Spock provides out of the box works fine for most people, but if you have special requirements, you may define your own set of commands. You can do this in a service provider like so: 59 | 60 | ``` php 61 | class YourServiceProvider extends ServiceProvider 62 | { 63 | public function boot() 64 | { 65 | // As a string, for a single command: 66 | app('spock')->setCommands('some-command'); 67 | 68 | // As an array for basic commands: 69 | app('spock')->setCommands(['command one', 'command two']); 70 | 71 | // A closure that returns an array of commands. 72 | // The first argument will be an instance of the `Commander` class. 73 | app('spock')->setCommands(function ($spock) { 74 | $paths = $spock->event()->affectedPaths(); 75 | // 76 | return [ ]; 77 | }); 78 | } 79 | } 80 | ``` 81 | 82 | ## Whitelisting Environments 83 | 84 | Spock will only run commands when it's in a whitelisted environment. By default, Spock will only run in the `production` environment. 85 | 86 | You can edit the environments in Spock addon settings area of the Control Panel, or add an `environments` array to `site/settings/addons/spock.yaml`, for example: 87 | 88 | ``` yaml 89 | environments: 90 | - production 91 | - staging 92 | ``` 93 | 94 | ## Queueing Commands 95 | 96 | Spock automatically queues commands to run in the background when you have a queue driver set. This can be helpful if you have git set to automatically push, or if you are running any commands that take time to execute, causing your users to experience wait time. To setup a queue in your statamic app: 97 | 98 | 1. Ensure [Redis](https://redis.io/) is installed and running on your server. 99 | 2. Add `QUEUE_DRIVER=redis` to your `.env` file. [What’s an .env file?](https://docs.statamic.com/environments) 100 | 3. Run `php please queue:listen`. 101 | 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spock for Statamic ![Statamic 2.10](https://img.shields.io/badge/statamic-2.10-blue.svg?style=flat-square) 2 | 3 | Perform commands when content has been published or changed. 4 | 5 | This addon essentially just listens for an event, and dispatches commands. Who better to listen and command than a Starship Commander with large ears? 6 | 7 | Its primary use is to automatically commit and push changes in production, but it can do anything your command line can. 8 | 9 | ## Documentation 10 | 11 | Read it on the [Statamic Marketplace](https://statamic.com/marketplace/addons/spock/docs) or contribute to it [here on GitHub](DOCUMENTATION.md). 12 | 13 | ## Requirements 14 | 15 | Statamic 2.10 is required. If you intend to run Spock on an earlier version of Statamic, please checkout Spock's [v1 branch](https://github.com/statamic/spock/tree/v1). 16 | 17 | ## Developing 18 | 19 | You can use [Kessel Run](https://github.com/jesseleite/kessel-run) to copy files to your Statamic installation, and run `php please test:addons` to run Spock's tests. 20 | -------------------------------------------------------------------------------- /addons/Spock/Commander.php: -------------------------------------------------------------------------------- 1 | shouldRunCommands()) { 26 | return; 27 | } 28 | 29 | $this->dispatch(new RunProcesses($this->commands())); 30 | } 31 | 32 | /** 33 | * Set the environment. 34 | * 35 | * @param string $environment 36 | * @return self 37 | */ 38 | public function environment($environment) 39 | { 40 | $this->environment = $environment; 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * Get or set the event. 47 | * 48 | * @param string $event 49 | * @return self 50 | */ 51 | public function event($event = null) 52 | { 53 | if (! $event) { 54 | return $this->event; 55 | } 56 | 57 | $this->event = $event; 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * Set the user that triggered the event. 64 | * 65 | * @param User $user 66 | * @return self 67 | */ 68 | public function user($user) 69 | { 70 | $this->user = $user; 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Set the Spock config. 77 | * 78 | * @param array $config 79 | * @return self 80 | */ 81 | public function config($config) 82 | { 83 | $this->config = $config; 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * Whether the commands should be run. 90 | * 91 | * @return bool 92 | */ 93 | public function shouldRunCommands() 94 | { 95 | return $this->isEnvironmentAllowed() && $this->isEventAllowed(); 96 | } 97 | 98 | /** 99 | * Is environment allowed? 100 | * 101 | * @return bool 102 | */ 103 | protected function isEnvironmentAllowed() 104 | { 105 | return in_array($this->environment, array_get($this->config, 'environments', [])); 106 | } 107 | 108 | /** 109 | * Is event allowed? 110 | * 111 | * @return bool 112 | */ 113 | protected function isEventAllowed() 114 | { 115 | return !in_array(get_class($this->event), array_get($this->config, 'ignore_events', [])); 116 | } 117 | 118 | /** 119 | * Set the commands to be run. 120 | * 121 | * @param array $commands 122 | * @return self 123 | */ 124 | public function setCommands($commands) 125 | { 126 | $this->commands = $commands; 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * Get the commands to be processed. 133 | * 134 | * @return Process[] 135 | */ 136 | public function commands() 137 | { 138 | $commands = $this->commands ?: $this->defaultCommands(); 139 | 140 | if ($commands instanceof \Closure) { 141 | $commands = $commands($this); 142 | } 143 | 144 | if (is_string($commands)) { 145 | $commands = [$commands]; 146 | } 147 | 148 | return array_map(function ($command) { 149 | return ($command instanceof Process) ? $command : new Process($command); 150 | }, $commands); 151 | } 152 | 153 | /** 154 | * Get the commands to be run if none have been specified. 155 | * 156 | * @return array 157 | */ 158 | protected function defaultCommands() 159 | { 160 | return (new Git($this->config, $this->event, $this->user))->commands(); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /addons/Spock/Git.php: -------------------------------------------------------------------------------- 1 | config = $config; 22 | $this->event = $event; 23 | $this->user = $user; 24 | } 25 | 26 | /** 27 | * Get the commands to be executed. 28 | * 29 | * @return array 30 | */ 31 | public function commands() 32 | { 33 | $commands = array_get($this->config, 'commands_before', []); 34 | 35 | foreach ($this->event->affectedPaths() as $path) { 36 | $path = escapeshellarg($path); 37 | $commands[] = "git add {$path}"; 38 | } 39 | 40 | $commands[] = $this->commitCommand(); 41 | 42 | if (array_get($this->config, 'git_push')) { 43 | $commands[] = 'git push'; 44 | } 45 | 46 | if ($after = array_get($this->config, 'commands_after', [])) { 47 | $commands = array_merge($commands, $after); 48 | } 49 | 50 | return $commands; 51 | } 52 | 53 | /** 54 | * Get the git commit command 55 | * 56 | * eg. The `git commit -m "The message"` bit. 57 | * 58 | * @return string 59 | */ 60 | protected function commitCommand() 61 | { 62 | $parts = ['git']; 63 | 64 | if ($username = array_get($this->config, 'git_username')) { 65 | $parts[] = sprintf('-c "user.name=%s"', $username); 66 | } 67 | 68 | if ($email = array_get($this->config, 'git_email')) { 69 | $parts[] = sprintf('-c "user.email=%s"', $email); 70 | } 71 | 72 | $message = 'commit -m "' . $this->label(); 73 | $message .= $this->user ? ' by ' . $this->user->username() : ''; 74 | $message .= '"'; 75 | $parts[] = $message; 76 | 77 | return join(' ', $parts); 78 | } 79 | 80 | /** 81 | * Get the label of the class, which is the action name. 82 | * 83 | * eg. "Statamic\Events\Data\DataSaved" becomes "Data saved" 84 | * 85 | * @return string 86 | */ 87 | protected function label() 88 | { 89 | $class = (new ReflectionClass($this->event))->getShortName(); 90 | 91 | return ucfirst(str_replace('_', ' ', snake_case($class))); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /addons/Spock/Process.php: -------------------------------------------------------------------------------- 1 | command = $command; 18 | } 19 | 20 | /** 21 | * Run the command. 22 | * 23 | * @throws ProcessFailedException 24 | */ 25 | public function run() 26 | { 27 | (new SymfonyProcess($this->command, BASE))->mustRun(); 28 | } 29 | 30 | /** 31 | * Get the command line to be run. 32 | * 33 | * @return string 34 | */ 35 | public function command() 36 | { 37 | return $this->command; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /addons/Spock/RunProcesses.php: -------------------------------------------------------------------------------- 1 | commands = $commands; 21 | } 22 | 23 | /** 24 | * Execute the job. 25 | */ 26 | public function handle() 27 | { 28 | foreach ($this->commands as $command) { 29 | $this->run($command); 30 | } 31 | } 32 | 33 | /** 34 | * Run individual command. 35 | * 36 | * @param Process $command 37 | */ 38 | public function run(Process $command) 39 | { 40 | try { 41 | $command->run(); 42 | } catch (ProcessFailedException $e) { 43 | $this->logFailedCommand($command, $e); 44 | } catch (\Exception $e) { 45 | app('log')->error($e); 46 | } 47 | } 48 | 49 | /** 50 | * Log failed command. 51 | * 52 | * @param Process $command 53 | * @param mixed $e 54 | */ 55 | protected function logFailedCommand(Process $command, $e) 56 | { 57 | $output = trim($e->getProcess()->getOutput()); 58 | $output = $output == '' ? 'No output' : "\n$output\n"; 59 | 60 | $error = trim($e->getProcess()->getErrorOutput()); 61 | $error = $error == '' ? 'No error' : "\n$error"; 62 | 63 | app('log')->error(vsprintf("Spock command exited unsuccessfully:\nCommand: %s\nOutput: %s\nError: %s", [ 64 | $command->command(), 65 | $output, 66 | $error 67 | ])); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /addons/Spock/SpockListener.php: -------------------------------------------------------------------------------- 1 | 'run', 18 | \Statamic\Events\Data\AssetUploaded::class => 'run', 19 | \Statamic\Events\Data\AssetMoved::class => 'run', 20 | \Statamic\Events\Data\AssetDeleted::class => 'run', 21 | \Statamic\Events\Data\AssetContainerSaved::class => 'run', 22 | \Statamic\Events\Data\AssetContainerDeleted::class => 'run', 23 | \Statamic\Events\Data\AssetFolderSaved::class => 'run', 24 | \Statamic\Events\Data\AssetFolderDeleted::class => 'run', 25 | \Statamic\Events\Data\CollectionSaved::class => 'run', 26 | \Statamic\Events\Data\CollectionDeleted::class => 'run', 27 | \Statamic\Events\Data\EntrySaved::class => 'run', 28 | \Statamic\Events\Data\EntryDeleted::class => 'run', 29 | \Statamic\Events\Data\FileUploaded::class => 'run', 30 | \Statamic\Events\Data\FieldsetSaved::class => 'run', 31 | \Statamic\Events\Data\FieldsetDeleted::class => 'run', 32 | \Statamic\Events\Data\FormSaved::class => 'run', 33 | \Statamic\Events\Data\GlobalsSaved::class => 'run', 34 | \Statamic\Events\Data\GlobalsDeleted::class => 'run', 35 | \Statamic\Events\Data\PageSaved::class => 'run', 36 | \Statamic\Events\Data\PageDeleted::class => 'run', 37 | \Statamic\Events\Data\PagesMoved::class => 'run', 38 | \Statamic\Events\Data\RoleSaved::class => 'run', 39 | \Statamic\Events\Data\RoleDeleted::class => 'run', 40 | \Statamic\Events\Data\SettingsSaved::class => 'run', 41 | \Statamic\Events\Data\SubmissionSaved::class => 'run', 42 | \Statamic\Events\Data\SubmissionDeleted::class => 'run', 43 | \Statamic\Events\Data\TaxonomySaved::class => 'run', 44 | \Statamic\Events\Data\TaxonomyDeleted::class => 'run', 45 | \Statamic\Events\Data\TermSaved::class => 'run', 46 | \Statamic\Events\Data\TermDeleted::class => 'run', 47 | \Statamic\Events\Data\UserSaved::class => 'run', 48 | \Statamic\Events\Data\UserDeleted::class => 'run', 49 | \Statamic\Events\Data\UserGroupSaved::class => 'run', 50 | \Statamic\Events\Data\UserGroupDeleted::class => 'run', 51 | ]; 52 | 53 | /** 54 | * Handle the event, run the command(s). 55 | * 56 | * @param DataEvent $event 57 | * @return void 58 | */ 59 | public function run(DataEvent $event) 60 | { 61 | app('spock') 62 | ->event($event) 63 | ->user(User::getCurrent()) 64 | ->handle(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /addons/Spock/SpockServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(Commander::class, function () { 12 | return (new Commander($this->app['log'])) 13 | ->config($this->getConfig()) 14 | ->environment($this->app->environment()); 15 | }); 16 | 17 | $this->app->alias(Commander::class, 'spock'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /addons/Spock/default.yaml: -------------------------------------------------------------------------------- 1 | # The names of the environments on which commands should be enabled. 2 | # If you're on an environment not listed here, nothing will happen. 3 | environments: 4 | - production 5 | 6 | # Whether the default git setup includes `git push` after committing. 7 | git_push: false 8 | 9 | # Class names of events to ignore. 10 | # For example, you may want to ignore Statamic\Events\Data\UserSaved 11 | # if you're gitignoring the users directory. 12 | ignore_events: [] 13 | 14 | # Extra commands to be run before and after the default git commands. 15 | # When customizing the Spock commands, these are ignored. 16 | commands_before: [] 17 | commands_after: [] 18 | -------------------------------------------------------------------------------- /addons/Spock/meta.yaml: -------------------------------------------------------------------------------- 1 | name: Spock 2 | version: '2.2.2' 3 | description: Perform commands when content has been published. 4 | developer: Statamic 5 | developer_url: https://statamic.com 6 | url: https://github.com/statamic/spock 7 | -------------------------------------------------------------------------------- /addons/Spock/resources/lang/en/settings.php: -------------------------------------------------------------------------------- 1 | environment(); 4 | 5 | return [ 6 | 'environments' => 'Environments', 7 | 'environments_instruct' => 'Spock will only run on the specified environments. The current environment is `'. app()->environment() . '`', 8 | 9 | 'git_push' => 'Git Push', 10 | 'git_push_instruct' => 'Whether Spock should perform a `git push` after the default git commands.', 11 | 12 | 'git_username' => 'Git Username', 13 | 'git_username_instruct' => "If left blank the default Git user's name is used.", 14 | 15 | 'git_email' => 'Git Email', 16 | 'git_email_instruct' => "If left blank the default Git user's email is used.", 17 | 18 | 'commands_before' => 'Commands Before', 19 | 'commands_before_instruct' => 'Commands to be run before the default git commands have been executed.', 20 | 21 | 'commands_after' => 'Commands After', 22 | 'commands_after_instruct' => 'Commands to be run after the default git commands have been executed.', 23 | 24 | 'ignore_events' => 'Ignore Events', 25 | 'ignore_events_instruct' => 'By default, Spock listens for all data-related events. You may choose to ignore specific ones.
Enter the full PHP class name, eg. `Statamic\Events\Data\UserSaved`', 26 | ]; 27 | -------------------------------------------------------------------------------- /addons/Spock/settings.yaml: -------------------------------------------------------------------------------- 1 | fields: 2 | environments: 3 | type: tags 4 | git_push: 5 | type: toggle 6 | git_username: 7 | type: text 8 | width: 50 9 | git_email: 10 | type: text 11 | width: 50 12 | commands_before: 13 | type: list 14 | width: 50 15 | commands_after: 16 | type: list 17 | width: 50 18 | ignore_events: 19 | type: list 20 | -------------------------------------------------------------------------------- /addons/Spock/tests/CommanderTest.php: -------------------------------------------------------------------------------- 1 | log = Mockery::spy(Log::class); 17 | 18 | app()->bind('log', function ($app) { 19 | return $this->log; 20 | }); 21 | 22 | app()->bind('Illuminate\Contracts\Bus\Dispatcher', function ($app) { 23 | return new FakeDispatcher; 24 | }); 25 | 26 | $this->commander = (new Commander($this->log)) 27 | ->config(['environments' => ['production']]) 28 | ->environment('production') 29 | ->event(new ExampleEvent); 30 | } 31 | 32 | public function tearDown() 33 | { 34 | Mockery::close(); 35 | } 36 | 37 | /** @test */ 38 | function only_runs_commands_for_whitelisted_environments() 39 | { 40 | $this->assertTrue($this->commander->shouldRunCommands()); 41 | 42 | $this->commander 43 | ->config(['environments' => ['one', 'two']]) 44 | ->environment('three'); 45 | 46 | $this->assertFalse($this->commander->shouldRunCommands()); 47 | } 48 | 49 | /** @test */ 50 | function does_not_run_command_if_event_is_ignored() 51 | { 52 | $this->commander->environment('production')->config([ 53 | 'environments' => ['production'], 54 | 'ignore_events' => [ExampleIgnoredEvent::class] 55 | ]); 56 | 57 | $this->assertTrue($this->commander->shouldRunCommands()); 58 | 59 | $this->commander->event(new ExampleEvent); 60 | $this->assertTrue($this->commander->shouldRunCommands()); 61 | 62 | $this->commander->event(new ExampleIgnoredEvent); 63 | $this->assertFalse($this->commander->shouldRunCommands()); 64 | } 65 | 66 | /** @test */ 67 | function command_strings_are_converted_to_objects() 68 | { 69 | $commands = ['echo one', 'echo two']; 70 | 71 | $this->commander->setCommands($commands); 72 | 73 | $this->assertEquals([ 74 | new Process('echo one'), 75 | new Process('echo two') 76 | ], $this->commander->commands()); 77 | } 78 | 79 | /** @test */ 80 | function a_command_as_a_string_is_converted_to_an_array() 81 | { 82 | $this->commander->setCommands('string'); 83 | 84 | $this->assertEquals([ 85 | new Process('string'), 86 | ], $this->commander->commands()); 87 | } 88 | 89 | /** @test */ 90 | function commands_are_run() 91 | { 92 | $this->commander->setCommands([ 93 | new Process('echo a command that will always work.') 94 | ])->handle(); 95 | 96 | $this->log->shouldNotReceive('error'); 97 | } 98 | 99 | /** @test */ 100 | function erroring_commands_are_logged() 101 | { 102 | $erroringProcess = Mockery::mock(Process::class); 103 | $genericException = new \Exception('A generic exception'); 104 | $erroringProcess->shouldReceive('run')->andThrow($genericException); 105 | 106 | $this->commander->setCommands([$erroringProcess])->handle(); 107 | 108 | $this->log->shouldHaveReceived('error')->with($genericException); 109 | } 110 | 111 | /** @test */ 112 | function failed_commands_are_logged() 113 | { 114 | $failingProcess = Mockery::mock(Process::class); 115 | $command = 'echo "some output"; nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'; 116 | $process = new SymfonyProcess($command); 117 | $process->run(); 118 | $e = new ProcessFailedException($process); 119 | $failingProcess->shouldReceive('run')->andThrow($e); 120 | $failingProcess->shouldReceive('command')->andReturn($command); 121 | 122 | $this->commander->setCommands([$failingProcess])->handle(); 123 | 124 | $this->log->shouldHaveReceived('error')->with(Mockery::on(function ($argument) use ($process) { 125 | return str_contains($argument, trim($process->getOutput())) 126 | && str_contains($argument, trim($process->getErrorOutput())); 127 | })); 128 | } 129 | 130 | /** @test */ 131 | function the_literal_string_no_output_is_shown_if_theres_no_output() 132 | { 133 | $failingProcess = Mockery::mock(Process::class); 134 | $command = 'nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'; 135 | $process = new SymfonyProcess($command); 136 | $process->run(); 137 | $e = new ProcessFailedException($process); 138 | $failingProcess->shouldReceive('run')->andThrow($e); 139 | $failingProcess->shouldReceive('command')->andReturn($command); 140 | 141 | $this->commander->setCommands([$failingProcess])->handle(); 142 | 143 | $this->log->shouldHaveReceived('error')->with(Mockery::on(function ($argument) use ($process) { 144 | return str_contains($argument, 'Output: No output') 145 | && str_contains($argument, trim($process->getErrorOutput())); 146 | })); 147 | } 148 | 149 | /** @test */ 150 | function the_literal_string_no_error_is_shown_if_theres_no_error() 151 | { 152 | $failingProcess = Mockery::mock(Process::class); 153 | $command = '(echo "some output"; exit 1)'; 154 | $process = new SymfonyProcess($command); 155 | $process->run(); 156 | $e = new ProcessFailedException($process); 157 | $failingProcess->shouldReceive('run')->andThrow($e); 158 | $failingProcess->shouldReceive('command')->andReturn($command); 159 | 160 | $this->commander->setCommands([$failingProcess])->handle(); 161 | 162 | $this->log->shouldHaveReceived('error')->with(Mockery::on(function ($argument) use ($process) { 163 | return str_contains($argument, trim($process->getOutput())) 164 | && str_contains($argument, 'Error: No error'); 165 | })); 166 | } 167 | 168 | /** @test */ 169 | function commands_can_be_a_closure() 170 | { 171 | $this->commander->event(new class { 172 | public function foo() { 173 | return 'bar'; 174 | } 175 | }); 176 | 177 | $this->commander->setCommands(function ($commander) { 178 | return [ 179 | 'hardcoded command', 180 | 'dynamic command ' . $commander->event()->foo() 181 | ]; 182 | }); 183 | 184 | $commands = $this->commander->commands(); 185 | 186 | $this->assertEquals([ 187 | new Process('hardcoded command'), 188 | new Process('dynamic command bar'), 189 | ], $commands); 190 | } 191 | } 192 | 193 | class ExampleEvent 194 | { 195 | // 196 | } 197 | 198 | class ExampleIgnoredEvent 199 | { 200 | // 201 | } 202 | 203 | class FakeDispatcher 204 | { 205 | public function dispatch($job) 206 | { 207 | $job->handle(); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /addons/Spock/tests/GitTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('username')->andReturn('johnsmith'); 17 | 18 | $git = new Git([], new DataSaved, $user); 19 | 20 | $this->assertEquals([ 21 | 'git add \'one.txt\'', 22 | 'git add \'two.txt\'', 23 | 'git commit -m "Data saved by johnsmith"', # Action is the "pretty" version of the class name. 24 | ], $git->commands()); 25 | } 26 | 27 | /** @test */ 28 | function commit_message_from_unauthenticated_user_contains_no_username() 29 | { 30 | $git = new Git([], new DataSaved, null); 31 | 32 | $this->assertEquals([ 33 | 'git add \'one.txt\'', 34 | 'git add \'two.txt\'', 35 | 'git commit -m "Data saved"', 36 | ], $git->commands()); 37 | } 38 | 39 | /** @test */ 40 | function git_push_gets_appended_if_specified_in_config() 41 | { 42 | $user = Mockery::mock(User::class); 43 | $user->shouldReceive('username')->andReturn('johnsmith'); 44 | 45 | $git = new Git(['git_push' => true], new DataSaved, $user); 46 | 47 | $this->assertEquals([ 48 | 'git add \'one.txt\'', 49 | 'git add \'two.txt\'', 50 | 'git commit -m "Data saved by johnsmith"', 51 | 'git push', 52 | ], $git->commands()); 53 | } 54 | 55 | /** @test */ 56 | function it_adds_commands_before_if_specified_in_config() 57 | { 58 | $user = Mockery::mock(User::class); 59 | $user->shouldReceive('username')->andReturn('johnsmith'); 60 | 61 | $git = new Git([ 62 | 'commands_before' => ['echo one', 'echo two'], 63 | ], new DataSaved, $user); 64 | 65 | $this->assertEquals([ 66 | 'echo one', 67 | 'echo two', 68 | 'git add \'one.txt\'', 69 | 'git add \'two.txt\'', 70 | 'git commit -m "Data saved by johnsmith"', 71 | ], $git->commands()); 72 | } 73 | 74 | /** @test */ 75 | function it_adds_commands_after_if_specified_in_config() 76 | { 77 | $user = Mockery::mock(User::class); 78 | $user->shouldReceive('username')->andReturn('johnsmith'); 79 | 80 | $git = new Git([ 81 | 'commands_after' => ['echo one', 'echo two'], 82 | ], new DataSaved, $user); 83 | 84 | $this->assertEquals([ 85 | 'git add \'one.txt\'', 86 | 'git add \'two.txt\'', 87 | 'git commit -m "Data saved by johnsmith"', 88 | 'echo one', 89 | 'echo two', 90 | ], $git->commands()); 91 | } 92 | 93 | /** @test */ 94 | function it_adds_username_to_commit_command_if_configured() 95 | { 96 | $git = new Git(['git_username' => 'Spock'], new DataSaved); 97 | 98 | $this->assertContains('git -c "user.name=Spock" commit -m "Data saved"', $git->commands()); 99 | } 100 | 101 | /** @test */ 102 | function it_adds_email_to_commit_command_if_configured() 103 | { 104 | $git = new Git(['git_email' => 'spock@domain.com'], new DataSaved); 105 | 106 | $this->assertContains('git -c "user.email=spock@domain.com" commit -m "Data saved"', $git->commands()); 107 | } 108 | 109 | /** @test */ 110 | function it_adds_email_and_username_to_commit_command_if_configured() 111 | { 112 | $git = new Git([ 113 | 'git_username' => 'Spock', 114 | 'git_email' => 'spock@domain.com' 115 | ], new DataSaved); 116 | 117 | $this->assertContains('git -c "user.name=Spock" -c "user.email=spock@domain.com" commit -m "Data saved"', $git->commands()); 118 | } 119 | } 120 | 121 | class DataSaved 122 | { 123 | public function affectedPaths() 124 | { 125 | return ['one.txt', 'two.txt']; 126 | } 127 | } 128 | --------------------------------------------------------------------------------