From e9b917fc2eca1f77070518fb06f37f7635c7ea53 Mon Sep 17 00:00:00 2001 From: Keith Brink Date: Fri, 28 Jul 2023 12:39:07 -0400 Subject: [PATCH 1/8] add push option --- src/Commands/SyncCommand.php | 69 ++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/src/Commands/SyncCommand.php b/src/Commands/SyncCommand.php index 88cb880..5a1a3bf 100644 --- a/src/Commands/SyncCommand.php +++ b/src/Commands/SyncCommand.php @@ -15,7 +15,7 @@ class SyncCommand extends Command { use UsesScheduleMonitoringModels; - public $signature = 'schedule-monitor:sync'; + public $signature = 'schedule-monitor:sync {--push}'; public $description = 'Sync the schedule of the app with the schedule monitor'; @@ -28,7 +28,7 @@ public function handle() $this ->syncScheduledTasksWithDatabase() - ->syncMonitoredScheduledTaskWithOhDear(); + ->sendMonitoredScheduledTasksToOhDear(); $monitoredScheduledTasksCount = $this->getMonitoredScheduleTaskModel()->count(); @@ -64,7 +64,8 @@ protected function syncScheduledTasksWithDatabase(): self return $this; } - protected function syncMonitoredScheduledTaskWithOhDear(): self + + protected function sendMonitoredScheduledTasksToOhDear(): self { if (! class_exists(OhDear::class)) { return $this; @@ -93,6 +94,34 @@ protected function syncMonitoredScheduledTaskWithOhDear(): self 'message' => 'Start syncing schedule with Oh Dear...', ])); + if($this->input('push')) { + $cronChecks = $this->pushMonitoredScheduledTaskToOhDear($siteId); + } else { + $cronChecks = $this->syncMonitoredScheduledTaskWithOhDear($siteId); + } + + render(view('schedule-monitor::alert', [ + 'message' => 'Successfully synced schedule with Oh Dear!', + 'class' => 'text-green', + ])); + + collect($cronChecks) + ->each( + function (CronCheck $cronCheck) { + if (! $monitoredScheduledTask = $this->getMonitoredScheduleTaskModel()->findForCronCheck($cronCheck)) { + return; + } + + $monitoredScheduledTask->update(['ping_url' => $cronCheck->pingUrl]); + $monitoredScheduledTask->markAsRegisteredOnOhDear(); + } + ); + + return $this; + } + + protected function syncMonitoredScheduledTaskWithOhDear(int $siteId): array + { $monitoredScheduledTasks = $this->getMonitoredScheduleTaskModel()->get(); $cronChecks = $monitoredScheduledTasks @@ -110,23 +139,27 @@ protected function syncMonitoredScheduledTaskWithOhDear(): self $cronChecks = app(OhDear::class)->site($siteId)->syncCronChecks($cronChecks); - render(view('schedule-monitor::alert', [ - 'message' => 'Successfully synced schedule with Oh Dear!', - 'class' => 'text-green', - ])); - - collect($cronChecks) - ->each( - function (CronCheck $cronCheck) { - if (! $monitoredScheduledTask = $this->getMonitoredScheduleTaskModel()->findForCronCheck($cronCheck)) { - return; - } + return $cronChecks; + } - $monitoredScheduledTask->update(['ping_url' => $cronCheck->pingUrl]); - $monitoredScheduledTask->markAsRegisteredOnOhDear(); - } + protected function pushMonitoredScheduledTaskToOhDear(int $siteId): array + { + $tasksToRegister = $this->getMonitoredScheduleTaskModel() + ->whereNull('registered_on_oh_dear_at') + ->get(); + + $cronChecks = []; + foreach($tasksToRegister as $taskToRegister) { + $cronChecks[] = app(OhDear::class)->createCronCheck( + siteId: $siteId, + name: $taskToRegister->name, + cronExpression: $taskToRegister->cron_expression, + graceTimeInMinutes: $taskToRegister->grace_time_in_minutes, + description: '', + serverTimezone: $taskToRegister->timezone, ); + } - return $this; + return $cronChecks; } } From 1c34977965d9c5ccc5db8e880fe5d0591f27877f Mon Sep 17 00:00:00 2001 From: Keith Brink Date: Fri, 28 Jul 2023 13:45:48 -0400 Subject: [PATCH 2/8] fix snapshot on pest --- composer.json | 1 + src/Commands/SyncCommand.php | 4 +- tests/Commands/SyncCommandTest.php | 43 ++++++++++++++++++- tests/TestClasses/FakeOhDear.php | 24 +++++++++++ ...he_schedule_with_the_db_and_oh_dear__1.yml | 0 ...ly_update_the_schedule_with_oh_dear__1.yml | 8 ++++ 6 files changed, 78 insertions(+), 2 deletions(-) rename tests/{Commands => }/__snapshots__/SyncCommandTest__it_can_sync_the_schedule_with_the_db_and_oh_dear__1.yml (100%) create mode 100644 tests/__snapshots__/SyncCommandTest__it_can_use_the_push_option_to_non_destructively_update_the_schedule_with_oh_dear__1.yml diff --git a/composer.json b/composer.json index d3c9f57..5fcb2df 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "orchestra/testbench": "^7.0|^8.0", "pestphp/pest": "^1.20", "pestphp/pest-plugin-laravel": "^1.2", + "spatie/pest-plugin-snapshots": "^1.1", "spatie/phpunit-snapshot-assertions": "^4.2", "spatie/test-time": "^1.2" }, diff --git a/src/Commands/SyncCommand.php b/src/Commands/SyncCommand.php index 5a1a3bf..4efdab0 100644 --- a/src/Commands/SyncCommand.php +++ b/src/Commands/SyncCommand.php @@ -94,7 +94,7 @@ protected function sendMonitoredScheduledTasksToOhDear(): self 'message' => 'Start syncing schedule with Oh Dear...', ])); - if($this->input('push')) { + if($this->option('push')) { $cronChecks = $this->pushMonitoredScheduledTaskToOhDear($siteId); } else { $cronChecks = $this->syncMonitoredScheduledTaskWithOhDear($siteId); @@ -148,6 +148,8 @@ protected function pushMonitoredScheduledTaskToOhDear(int $siteId): array ->whereNull('registered_on_oh_dear_at') ->get(); + ray($tasksToRegister); + $cronChecks = []; foreach($tasksToRegister as $taskToRegister) { $cronChecks[] = app(OhDear::class)->createCronCheck( diff --git a/tests/Commands/SyncCommandTest.php b/tests/Commands/SyncCommandTest.php index b6d3b2b..d326a40 100644 --- a/tests/Commands/SyncCommandTest.php +++ b/tests/Commands/SyncCommandTest.php @@ -6,6 +6,7 @@ use Spatie\ScheduleMonitor\Tests\TestClasses\TestJob; use Spatie\ScheduleMonitor\Tests\TestClasses\TestKernel; use Spatie\TestTime\TestTime; +use function Spatie\Snapshots\{assertMatchesSnapshot}; beforeEach(function () { TestTime::freeze('Y-m-d H:i:s', '2020-01-01 00:00:00'); @@ -76,7 +77,47 @@ 'timezone' => 'Asia/Kolkata', ]); - $this->assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); + assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); +}); + +it('can use the push option to non destructively update the schedule with oh dear', function () { + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy-1')->everyMinute(); + $schedule->command('dummy-2')->hourly(); + }); + + MonitoredScheduledTask::create([ + 'name' => 'dummy-1', + 'type' => 'command', + 'cron_expression' => '* * * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy-1', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + + $this->artisan(SyncCommand::class, ['--push' => true]); + + $monitoredScheduledTasks = MonitoredScheduledTask::get(); + expect($monitoredScheduledTasks)->toHaveCount(2); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => 'dummy-2', + 'type' => 'command', + 'cron_expression' => '0 * * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy-2', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + + assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); }); it('will not monitor commands without a name', function () { diff --git a/tests/TestClasses/FakeOhDear.php b/tests/TestClasses/FakeOhDear.php index fc87306..f8fcdb1 100644 --- a/tests/TestClasses/FakeOhDear.php +++ b/tests/TestClasses/FakeOhDear.php @@ -28,6 +28,30 @@ public function getSyncedCronCheckAttributes(): array { return $this->syncedCronCheckAttributes; } + + public function createCronCheck( + int $siteId, + string $name, + string $cronExpression, + int $graceTimeInMinutes, + $description, + string $serverTimezone + ): CronCheck { + $attributes = [ + 'name' => $name, + 'type' => 'cron', + 'cron_expression' => $cronExpression, + 'grace_time_in_minutes' => $graceTimeInMinutes, + 'description' => $description ?? '', + 'server_timezone' => $serverTimezone, + ]; + + $attributes['ping_url'] = 'https://ping.ohdear.app/test-ping-url-' . urlencode($attributes['name']); + + $this->syncedCronCheckAttributes[] = $attributes; + + return new CronCheck($attributes, $this); + } } class FakeSite extends Site diff --git a/tests/Commands/__snapshots__/SyncCommandTest__it_can_sync_the_schedule_with_the_db_and_oh_dear__1.yml b/tests/__snapshots__/SyncCommandTest__it_can_sync_the_schedule_with_the_db_and_oh_dear__1.yml similarity index 100% rename from tests/Commands/__snapshots__/SyncCommandTest__it_can_sync_the_schedule_with_the_db_and_oh_dear__1.yml rename to tests/__snapshots__/SyncCommandTest__it_can_sync_the_schedule_with_the_db_and_oh_dear__1.yml diff --git a/tests/__snapshots__/SyncCommandTest__it_can_use_the_push_option_to_non_destructively_update_the_schedule_with_oh_dear__1.yml b/tests/__snapshots__/SyncCommandTest__it_can_use_the_push_option_to_non_destructively_update_the_schedule_with_oh_dear__1.yml new file mode 100644 index 0000000..cc372d8 --- /dev/null +++ b/tests/__snapshots__/SyncCommandTest__it_can_use_the_push_option_to_non_destructively_update_the_schedule_with_oh_dear__1.yml @@ -0,0 +1,8 @@ +- + name: dummy-2 + type: cron + cron_expression: '0 * * * *' + grace_time_in_minutes: 5 + description: '' + server_timezone: UTC + ping_url: 'https://ping.ohdear.app/test-ping-url-dummy-2' From bb507640abde0a1c186b706ff4752fb8578e389c Mon Sep 17 00:00:00 2001 From: Keith Brink Date: Fri, 28 Jul 2023 13:49:27 -0400 Subject: [PATCH 3/8] add readme section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f55bcd5..8a22387 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ This command is responsible for syncing your schedule with the database, and opt In a non-production environment you should manually run `schedule-monitor:sync`. You can verify if everything synced correctly using `schedule-monitor:list`. -**Note:** Running the sync command will remove any other cron monitors that you've defined other than the application schedule. +If you would like to use non-destructive syncs to Oh Dear so that you can monitor other cron tasks outside of Laravel, you can use the `--push` flag. This will only push new tasks to Oh Dear, rather than a full sync. Note that this will not remove any tasks from Oh Dear that are no longer in your schedule. ## Usage From 1f6d1cec39ab8e0af242617193f7800781974377 Mon Sep 17 00:00:00 2001 From: Keith Brink Date: Fri, 28 Jul 2023 14:00:25 -0400 Subject: [PATCH 4/8] Keep the note about destructive sync --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8a22387..fcc3057 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,8 @@ This command is responsible for syncing your schedule with the database, and opt In a non-production environment you should manually run `schedule-monitor:sync`. You can verify if everything synced correctly using `schedule-monitor:list`. +**Note:** Running the sync command will remove any other cron monitors that you've defined other than the application schedule. + If you would like to use non-destructive syncs to Oh Dear so that you can monitor other cron tasks outside of Laravel, you can use the `--push` flag. This will only push new tasks to Oh Dear, rather than a full sync. Note that this will not remove any tasks from Oh Dear that are no longer in your schedule. ## Usage From 500381cd2e3f2c4913095f044ad527dd4926e0cf Mon Sep 17 00:00:00 2001 From: Keith Brink Date: Fri, 28 Jul 2023 14:02:03 -0400 Subject: [PATCH 5/8] Use match on push option --- src/Commands/SyncCommand.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Commands/SyncCommand.php b/src/Commands/SyncCommand.php index 4efdab0..1f9fa8c 100644 --- a/src/Commands/SyncCommand.php +++ b/src/Commands/SyncCommand.php @@ -94,11 +94,10 @@ protected function sendMonitoredScheduledTasksToOhDear(): self 'message' => 'Start syncing schedule with Oh Dear...', ])); - if($this->option('push')) { - $cronChecks = $this->pushMonitoredScheduledTaskToOhDear($siteId); - } else { - $cronChecks = $this->syncMonitoredScheduledTaskWithOhDear($siteId); - } + $cronChecks = match($this->option('push')) { + true => $this->pushMonitoredScheduledTaskToOhDear($siteId), + default => $this->syncMonitoredScheduledTaskWithOhDear($siteId), + }; render(view('schedule-monitor::alert', [ 'message' => 'Successfully synced schedule with Oh Dear!', @@ -148,8 +147,6 @@ protected function pushMonitoredScheduledTaskToOhDear(int $siteId): array ->whereNull('registered_on_oh_dear_at') ->get(); - ray($tasksToRegister); - $cronChecks = []; foreach($tasksToRegister as $taskToRegister) { $cronChecks[] = app(OhDear::class)->createCronCheck( From dc33aed8b146ca8165054016af68da577a062c7b Mon Sep 17 00:00:00 2001 From: Keith Brink Date: Mon, 31 Jul 2023 15:02:32 -0400 Subject: [PATCH 6/8] Non destructive db updates --- src/Commands/SyncCommand.php | 31 +++++++------- tests/Commands/SyncCommandTest.php | 42 +++++++++++++++---- ...e_the_schedule_with_db_and_oh_dear__1.yml} | 8 ++++ 3 files changed, 58 insertions(+), 23 deletions(-) rename tests/__snapshots__/{SyncCommandTest__it_can_use_the_push_option_to_non_destructively_update_the_schedule_with_oh_dear__1.yml => SyncCommandTest__it_can_use_the_keep_old_option_to_non_destructively_update_the_schedule_with_db_and_oh_dear__1.yml} (50%) diff --git a/src/Commands/SyncCommand.php b/src/Commands/SyncCommand.php index 1f9fa8c..e91c44a 100644 --- a/src/Commands/SyncCommand.php +++ b/src/Commands/SyncCommand.php @@ -9,13 +9,14 @@ use Spatie\ScheduleMonitor\Support\Concerns\UsesScheduleMonitoringModels; use Spatie\ScheduleMonitor\Support\ScheduledTasks\ScheduledTasks; use Spatie\ScheduleMonitor\Support\ScheduledTasks\Tasks\Task; + use function Termwind\render; class SyncCommand extends Command { use UsesScheduleMonitoringModels; - public $signature = 'schedule-monitor:sync {--push}'; + public $signature = 'schedule-monitor:sync {--keep-old}'; public $description = 'Sync the schedule of the app with the schedule monitor'; @@ -27,8 +28,8 @@ public function handle() ])); $this - ->syncScheduledTasksWithDatabase() - ->sendMonitoredScheduledTasksToOhDear(); + ->storeScheduledTasksInDatabase() + ->storeMonitoredScheduledTasksInOhDear(); $monitoredScheduledTasksCount = $this->getMonitoredScheduleTaskModel()->count(); @@ -37,7 +38,7 @@ public function handle() ])); } - protected function syncScheduledTasksWithDatabase(): self + protected function storeScheduledTasksInDatabase(): self { render(view('schedule-monitor::alert', [ 'message' => 'Start syncing schedule with database...', @@ -57,15 +58,16 @@ protected function syncScheduledTasksWithDatabase(): self ); }); - $this->getMonitoredScheduleTaskModel()->query() - ->whereNotIn('id', $monitoredScheduledTasks->pluck('id')) - ->delete(); + if (! $this->option('keep-old')) { + $this->getMonitoredScheduleTaskModel()->query() + ->whereNotIn('id', $monitoredScheduledTasks->pluck('id')) + ->delete(); + } return $this; } - - protected function sendMonitoredScheduledTasksToOhDear(): self + protected function storeMonitoredScheduledTasksInOhDear(): self { if (! class_exists(OhDear::class)) { return $this; @@ -94,10 +96,9 @@ protected function sendMonitoredScheduledTasksToOhDear(): self 'message' => 'Start syncing schedule with Oh Dear...', ])); - $cronChecks = match($this->option('push')) { - true => $this->pushMonitoredScheduledTaskToOhDear($siteId), - default => $this->syncMonitoredScheduledTaskWithOhDear($siteId), - }; + $cronChecks = $this->option('keep-old') + ? $this->pushMonitoredScheduledTaskToOhDear($siteId) + : $this->syncMonitoredScheduledTaskWithOhDear($siteId); render(view('schedule-monitor::alert', [ 'message' => 'Successfully synced schedule with Oh Dear!', @@ -142,13 +143,13 @@ protected function syncMonitoredScheduledTaskWithOhDear(int $siteId): array } protected function pushMonitoredScheduledTaskToOhDear(int $siteId): array - { + { $tasksToRegister = $this->getMonitoredScheduleTaskModel() ->whereNull('registered_on_oh_dear_at') ->get(); $cronChecks = []; - foreach($tasksToRegister as $taskToRegister) { + foreach ($tasksToRegister as $taskToRegister) { $cronChecks[] = app(OhDear::class)->createCronCheck( siteId: $siteId, name: $taskToRegister->name, diff --git a/tests/Commands/SyncCommandTest.php b/tests/Commands/SyncCommandTest.php index d326a40..88a4b57 100644 --- a/tests/Commands/SyncCommandTest.php +++ b/tests/Commands/SyncCommandTest.php @@ -80,12 +80,7 @@ assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); }); -it('can use the push option to non destructively update the schedule with oh dear', function () { - TestKernel::registerScheduledTasks(function (Schedule $schedule) { - $schedule->command('dummy-1')->everyMinute(); - $schedule->command('dummy-2')->hourly(); - }); - +it('can use the keep old option to non destructively update the schedule with db and oh dear', function () { MonitoredScheduledTask::create([ 'name' => 'dummy-1', 'type' => 'command', @@ -99,10 +94,28 @@ 'timezone' => 'UTC', ]); - $this->artisan(SyncCommand::class, ['--push' => true]); + TestKernel::registerScheduledTasks(function (Schedule $schedule) { + $schedule->command('dummy-2')->hourly(); + $schedule->command('dummy-3')->daily(); + }); + + $this->artisan(SyncCommand::class, ['--keep-old' => true]); $monitoredScheduledTasks = MonitoredScheduledTask::get(); - expect($monitoredScheduledTasks)->toHaveCount(2); + expect($monitoredScheduledTasks)->toHaveCount(3); + + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => 'dummy-1', + 'type' => 'command', + 'cron_expression' => '* * * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy-1', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); $this->assertDatabaseHas('monitored_scheduled_tasks', [ 'name' => 'dummy-2', @@ -117,6 +130,19 @@ 'timezone' => 'UTC', ]); + $this->assertDatabaseHas('monitored_scheduled_tasks', [ + 'name' => 'dummy-3', + 'type' => 'command', + 'cron_expression' => '0 0 * * *', + 'ping_url' => 'https://ping.ohdear.app/test-ping-url-dummy-3', + 'registered_on_oh_dear_at' => now()->format('Y-m-d H:i:s'), + 'grace_time_in_minutes' => 5, + 'last_pinged_at' => null, + 'last_started_at' => null, + 'last_finished_at' => null, + 'timezone' => 'UTC', + ]); + assertMatchesSnapshot($this->ohDear->getSyncedCronCheckAttributes()); }); diff --git a/tests/__snapshots__/SyncCommandTest__it_can_use_the_push_option_to_non_destructively_update_the_schedule_with_oh_dear__1.yml b/tests/__snapshots__/SyncCommandTest__it_can_use_the_keep_old_option_to_non_destructively_update_the_schedule_with_db_and_oh_dear__1.yml similarity index 50% rename from tests/__snapshots__/SyncCommandTest__it_can_use_the_push_option_to_non_destructively_update_the_schedule_with_oh_dear__1.yml rename to tests/__snapshots__/SyncCommandTest__it_can_use_the_keep_old_option_to_non_destructively_update_the_schedule_with_db_and_oh_dear__1.yml index cc372d8..35180ba 100644 --- a/tests/__snapshots__/SyncCommandTest__it_can_use_the_push_option_to_non_destructively_update_the_schedule_with_oh_dear__1.yml +++ b/tests/__snapshots__/SyncCommandTest__it_can_use_the_keep_old_option_to_non_destructively_update_the_schedule_with_db_and_oh_dear__1.yml @@ -6,3 +6,11 @@ description: '' server_timezone: UTC ping_url: 'https://ping.ohdear.app/test-ping-url-dummy-2' +- + name: dummy-3 + type: cron + cron_expression: '0 0 * * *' + grace_time_in_minutes: 5 + description: '' + server_timezone: UTC + ping_url: 'https://ping.ohdear.app/test-ping-url-dummy-3' From 6ea4e43e536f2953f21b71df33dd530bf15ef136 Mon Sep 17 00:00:00 2001 From: Keith Brink Date: Mon, 31 Jul 2023 15:05:33 -0400 Subject: [PATCH 7/8] formatting --- src/Commands/SyncCommand.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Commands/SyncCommand.php b/src/Commands/SyncCommand.php index e91c44a..6511f4b 100644 --- a/src/Commands/SyncCommand.php +++ b/src/Commands/SyncCommand.php @@ -9,7 +9,6 @@ use Spatie\ScheduleMonitor\Support\Concerns\UsesScheduleMonitoringModels; use Spatie\ScheduleMonitor\Support\ScheduledTasks\ScheduledTasks; use Spatie\ScheduleMonitor\Support\ScheduledTasks\Tasks\Task; - use function Termwind\render; class SyncCommand extends Command From f0ae1e22b0c942e3ae3cf5400aa81f78237eefd9 Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Tue, 1 Aug 2023 11:38:41 +0200 Subject: [PATCH 8/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fcc3057..2436347 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ In a non-production environment you should manually run `schedule-monitor:sync`. **Note:** Running the sync command will remove any other cron monitors that you've defined other than the application schedule. -If you would like to use non-destructive syncs to Oh Dear so that you can monitor other cron tasks outside of Laravel, you can use the `--push` flag. This will only push new tasks to Oh Dear, rather than a full sync. Note that this will not remove any tasks from Oh Dear that are no longer in your schedule. +If you would like to use non-destructive syncs to Oh Dear so that you can monitor other cron tasks outside of Laravel, you can use the `--keep-old` flag. This will only push new tasks to Oh Dear, rather than a full sync. Note that this will not remove any tasks from Oh Dear that are no longer in your schedule. ## Usage