diff --git a/.travis.yml b/.travis.yml index c42dfcf..bb3ad53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,4 @@ before_script: - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-dist script: - - vendor/bin/phpunit --stop-on-failure + - vendor/bin/phpunit diff --git a/src/Commands/InstallCommand.php b/src/Commands/InstallCommand.php index 112776d..b671f0e 100644 --- a/src/Commands/InstallCommand.php +++ b/src/Commands/InstallCommand.php @@ -7,7 +7,7 @@ class InstallCommand extends Command { - protected $signature = 'hook:install {name} {version?} {--enable}'; + protected $signature = 'hook:install {name} {version?} {--enable} {--no-migrate} {--no-seed} {--no-publish}'; protected $description = 'Download and install a hook from remote https://larapack.io'; @@ -29,7 +29,13 @@ public function handle() { $name = $this->argument('name'); - $this->hooks->install($name, $this->argument('version')); + $this->hooks->install( + $name, + $this->argument('version'), + !$this->option('no-migrate'), + !$this->option('no-seed'), + !$this->option('no-publish') + ); if ($this->option('enable')) { $this->hooks->enable($name); diff --git a/src/Commands/UninstallCommand.php b/src/Commands/UninstallCommand.php index 633a0ef..8db6449 100644 --- a/src/Commands/UninstallCommand.php +++ b/src/Commands/UninstallCommand.php @@ -7,7 +7,7 @@ class UninstallCommand extends Command { - protected $signature = 'hook:uninstall {name} {--delete}'; + protected $signature = 'hook:uninstall {name} {--delete} {--no-unmigrate} {--no-unseed} {--no-unpublish}'; protected $description = 'Uninstall a hook'; @@ -29,7 +29,13 @@ public function handle() { $name = $this->argument('name'); - $this->hooks->uninstall($name, $this->option('delete')); + $this->hooks->uninstall( + $name, + $this->option('delete'), + !$this->option('no-unmigrate'), + !$this->option('no-unseed'), + !$this->option('no-unpublish') + ); $this->info("Hook [{$name}] have been uninstalled."); } diff --git a/src/Commands/UpdateCommand.php b/src/Commands/UpdateCommand.php index 2c72250..1dd73e2 100644 --- a/src/Commands/UpdateCommand.php +++ b/src/Commands/UpdateCommand.php @@ -7,7 +7,7 @@ class UpdateCommand extends Command { - protected $signature = 'hook:update {name} {version?}'; + protected $signature = 'hook:update {name} {version?} {--no-migrate} {--no-seed} {--no-publish} {--force}'; protected $description = 'Update a hook'; @@ -35,10 +35,17 @@ public function handle() $hook = $hooks->where('name', $name)->first(); - if ($this->hooks->update($name, $version)) { - return $this->info("Hook [{$name}] have been updated!"); - } - - return $this->info('Nothing to update.'); + $updated = $this->hooks->update( + $name, + $version, + !$this->option('no-migrate'), + !$this->option('no-seed'), + !$this->option('no-publish'), + $this->option('force') + ); + + return $updated + ? $this->info("Hook [{$name}] have been updated!") + : $this->info('Nothing to update.'); } } diff --git a/src/Hook.php b/src/Hook.php index 988cadb..80f3c44 100644 --- a/src/Hook.php +++ b/src/Hook.php @@ -67,15 +67,18 @@ public function loadComposerJson() $this->composerJson = json_decode($this->getComposerJsonFile(), true); } - public function getComposerJsonFile() + public function getPath() { - $path = 'vendor/'.$this->name; - if ($this->isLocal()) { - $path = 'hooks/'.$this->name; + return base_path('hooks/'.$this->name); } - return $this->filesystem->get(base_path($path.'/composer.json')); + return base_path('vendor/'.$this->name); + } + + public function getComposerJsonFile() + { + return $this->filesystem->get($this->getPath().'/composer.json'); } public function setLatest($latest) @@ -106,7 +109,7 @@ public function update(array $parameters) public function outdated() { if (is_null($this->latest)) { - $this->latest = app('hooks')->outdated($hook); + $this->latest = app('hooks')->outdated($this->name); } return $this->latest != $this->version; diff --git a/src/Hooks.php b/src/Hooks.php index becfc54..da11e21 100644 --- a/src/Hooks.php +++ b/src/Hooks.php @@ -3,6 +3,7 @@ namespace Larapack\Hooks; use Carbon\Carbon; +use Illuminate\Database\Migrations\Migrator; use Illuminate\Filesystem\Filesystem; use Symfony\Component\Console\Input\ArrayInput; @@ -11,6 +12,7 @@ class Hooks protected static $remote = 'https://larapack.io'; protected $filesystem; + protected $migrator; protected $hooks; protected $lastRemoteCheck; protected $outdated = []; @@ -18,16 +20,22 @@ class Hooks protected $composer; protected $composerOutput; + protected $tempDirectories = []; + protected static $memoryLimit = null; protected static $memoryLimitSet = false; protected static $useVersionWildcardOnUpdate = false; protected static $versionWildcard = '*'; - protected static $localVersion = 'dev-master'; + protected static $localVersion = '*'; + + protected static $fakeDateTime = false; - public function __construct(Filesystem $filesystem) + public function __construct(Filesystem $filesystem, Migrator $migrator) { $this->filesystem = $filesystem; + $this->migrator = $migrator; + $this->prepareComposer(); $this->readOutdated(); $this->readJsonFile(); @@ -64,6 +72,11 @@ public static function getUseVersionWildcardOnUpdate() return static::$useVersionWildcardOnUpdate; } + public static function fakeDateTime(Carbon $dateTime) + { + static::$fakeDateTime = $dateTime; + } + public function readOutdated() { $file = base_path('hooks/outdated.json'); @@ -180,7 +193,7 @@ public function setLastRemoteCheck(Carbon $carbon) * * @throws \Larapack\Hooks\Exceptions\HookAlreadyInstalledException */ - public function install($name, $version = null) + public function install($name, $version = null, $migrate = true, $seed = true, $publish = true) { // Check if already installed if ($this->installed($name)) { @@ -216,6 +229,18 @@ public function install($name, $version = null) $this->readJsonFile([$name]); $this->remakeJson(); + if ($migrate) { + $this->migrateHook($this->hooks[$name]); + } + + if ($seed) { + $this->seedHook($this->hooks[$name]); + } + + if ($publish) { + $this->publishHook($this->hooks[$name]); + } + event(new Events\InstalledHook($this->hooks[$name])); } @@ -237,7 +262,7 @@ public function prepareLocalInstallation($name) * * @throws \Larapack\Hooks\Exceptions\HookNotInstalledException */ - public function uninstall($name, $delete = false) + public function uninstall($name, $delete = false, $unmigrate = true, $unseed = true, $unpublish = true) { // Check if installed if (!$this->installed($name)) { @@ -253,12 +278,22 @@ public function uninstall($name, $delete = false) if ($this->enabled($name)) { event(new Events\DisablingHook($hook)); - //$this->runScripts($name, 'disable'); + // Some logic could later be placed here event(new Events\DisabledHook($hook)); } - // TODO: Run scripts for uninstall + if ($unseed) { + $this->unseedHook($hook); + } + + if ($unmigrate) { + $this->unmigrateHook($hook); + } + + if ($unpublish) { + $this->unpublishHook($hook); + } $this->runComposer([ 'command' => 'remove', @@ -282,13 +317,17 @@ public function uninstall($name, $delete = false) * * @param $name * @param string|null $version + * @param bool $migrate + * @param bool $seed + * @param bool $publish + * @param bool $force * * @throws \Larapack\Hooks\Exceptions\HookNotFoundException * @throws \Larapack\Hooks\Exceptions\HookNotInstalledException * * @return bool */ - public function update($name, $version = null) + public function update($name, $version, $migrate = true, $seed = true, $publish = true, $force = false) { // Check if hook exists if (!$this->downloaded($name)) { @@ -313,6 +352,10 @@ public function update($name, $version = null) } } + if (!$force) { + $this->makeTemponaryBackup($this->hooks[$name]); + } + // Require hook if (is_null($version)) { $this->composerRequire([$name]); // TODO: Save Composer output somewhere @@ -331,7 +374,19 @@ public function update($name, $version = null) $this->readJsonFile(); $this->remakeJson(); - // TODO: Run scripts for update + if ($migrate) { + $this->migrateHook($this->hooks[$name]); + } + + if ($seed) { + $this->seedHook($this->hooks[$name]); + } + + if ($publish) { + $this->publishHook($this->hooks[$name], $force); + } + + $this->clearTemponaryFiles(); event(new Events\UpdatedHook($this->hooks[$name])); @@ -368,8 +423,6 @@ public function enable($name) event(new Events\EnablingHook($hook)); - // TODO: Run scripts for enable - $this->hooks[$name]->update(['enabled' => true]); $this->remakeJson(); @@ -407,8 +460,6 @@ public function disable($name) event(new Events\DisablingHook($hook)); - // TODO: Run scripts for disable - $this->hooks[$name]->update(['enabled' => false]); $this->remakeJson(); @@ -452,10 +503,11 @@ public function make($name) protected function makeStubFiles($name) { $replaces = [ - 'kebab-case' => $name, - 'snake_case' => snake_case($name), - 'camcelCase' => camel_case($name), - 'StudlyCase' => studly_case($name), + 'kebab-case' => $name, + 'snake_case' => snake_case(str_replace('-', '_', $name)), + 'camelCase' => camel_case(str_replace('-', '_', $name)), + 'StudlyCase' => studly_case(str_replace('-', '_', $name)), + 'MIGRATION_DATE_TIME' => $this->migrationDateTimeString(), ]; $files = $this->filesystem->allFiles(__DIR__.'/../stub'); @@ -482,6 +534,17 @@ protected function makeStubFiles($name) } } + protected function migrationDateTimeString() + { + $dateTime = Carbon::now(); + + if (static::$fakeDateTime) { + $dateTime = static::$fakeDateTime; + } + + return $dateTime->format('Y_m_d_His'); + } + protected function replace($content, array $replaces) { return str_replace(array_keys($replaces), array_values($replaces), $content); @@ -832,6 +895,221 @@ public function checkForUpdates() return collect($hooks); } + + /** + * Run migrations found for a specific hook. + * + * @param \Larapack\Hooks\Hook $hook + */ + protected function migrateHook(Hook $hook) + { + $migrations = (array) $hook->getComposerHookKey('migrations', []); + + $this->migrator->run($this->realPath($migrations, $hook->getPath().'/')->all()); + } + + /** + * Rollback migrations found for a specific hook. + * + * @param \Larapack\Hooks\Hook $hook + */ + protected function unmigrateHook(Hook $hook) + { + $migrations = (array) $hook->getComposerHookKey('migrations', []); + + $this->migrator->reset($this->realPath($migrations, $hook->getPath().'/')->all()); + } + + /** + * Run seeders found for a specific hook. + * + * @param \Larapack\Hooks\Hook $hook + */ + protected function seedHook(Hook $hook) + { + $folders = (array) $hook->getComposerHookKey('seeders', []); + $basePath = $hook->getPath().'/'; + + $this->runSeeders($folders, $basePath); + } + + /** + * Run unseeders found for a specific hook. + * + * @param \Larapack\Hooks\Hook $hook + */ + protected function unseedHook(Hook $hook) + { + $folders = (array) $hook->getComposerHookKey('unseeders', []); + $basePath = $hook->getPath().'/'; + + $this->runSeeders($folders, $basePath); + } + + /** + * Publish assets found for a specific hook. + * + * @param \Larapack\Hooks\Hook $hook + */ + protected function publishHook(Hook $hook, $force = false) + { + $folders = (array) $hook->getComposerHookKey('assets', []); + $basePath = $hook->getPath().'/'; + + $filesystem = $this->filesystem; + foreach ($folders as $location => $publish) { + $publishPath = base_path($publish); + if (!$realLocation = realpath($basePath.$location)) { + continue; + } + + $allFiles = collect($this->filesystem->allFiles($realLocation)) + ->map(function ($file) use ($realLocation) { + return substr($file->getRealPath(), strlen($realLocation) + 1); + }); + + $newFiles = $allFiles->filter(function ($filename) use ($publishPath, $filesystem) { + return !$filesystem->exists($publishPath.'/'.$filename); + }); + + $updatedFiles = $allFiles->filter(function ($filename) use ($publishPath, $filesystem) { + return $filesystem->exists($publishPath.'/'.$filename); + }); + + if (!$force && isset($this->tempDirectories[$hook->name])) { + $tempLocation = $this->tempDirectories[$hook->name].'/'.$location; + $updatedFiles = $updatedFiles + ->filter(function ($filename) use ($tempLocation, $publishPath, $filesystem) { + if (!$filesystem->exists($tempLocation.'/'.$filename)) { + return true; + } + + return md5_file($tempLocation.'/'.$filename) == + md5_file($publishPath.'/'.$filename); + }); + } + + $newFiles->merge($updatedFiles) + ->each(function ($filename) use ($realLocation, $publishPath, $filesystem) { + $directory = substr($publishPath.'/'.$filename, 0, -strlen(basename($filename))); + + if (!$filesystem->isDirectory($directory)) { + $filesystem->makeDirectory($directory, 0755, true, true); + } + + $filesystem->delete($publishPath.'/'.$filename); + $filesystem->copy( + $realLocation.'/'.$filename, + $publishPath.'/'.$filename + ); + }); + } + } + + /** + * Unpublish assets found for a specific hook. + * + * @param \Larapack\Hooks\Hook $hook + */ + protected function unpublishHook(Hook $hook) + { + $folders = (array) $hook->getComposerHookKey('assets', []); + $basePath = $hook->getPath().'/'; + + $filesystem = $this->filesystem; + foreach ($folders as $location => $publish) { + $publishPath = base_path($publish); + if (!$realLocation = realpath($basePath.$location)) { + continue; + } + + $allFiles = collect($this->filesystem->allFiles($realLocation)) + ->map(function ($file) use ($realLocation) { + return substr($file->getRealPath(), strlen($realLocation) + 1); + }); + + $existingFiles = $allFiles->filter(function ($filename) use ($publishPath, $filesystem) { + return $filesystem->exists($publishPath.'/'.$filename); + }); + + $existingFiles->each(function ($filename) use ($publishPath, $filesystem) { + $filesystem->delete($publishPath.'/'.$filename); + }); + } + } + + /** + * Run seeder files. + * + * @param array $folders + * @param string $basePath + */ + protected function runSeeders($folders, $basePath) + { + $filesystem = $this->filesystem; + + $this->realPath($folders, $basePath) + ->each(function ($folder) use ($filesystem) { + collect($filesystem->files($folder))->filter(function ($file) { + return $file->getExtension() == 'php'; + })->each(function ($file) { + $class = substr($file->getFilename(), 0, -4); + require_once $file->getRealPath(); + + (new $class())->run(); + }); + }); + } + + /** + * Get collection of realpath paths. + * + * @param array $paths + * @param string $basePath + * + * @return \Illuminate\Support\Collection + */ + protected function realPath(array $paths, $basePath = '') + { + return collect($paths)->map(function ($path) use ($basePath) { + return realpath($basePath.$path); + })->filter(function ($path) { + return $path; + }); + } + + /** + * Make temponary backup of hook. + * + * @param \Larapack\Hooks\Hook $hook + * + * @return void + */ + protected function makeTemponaryBackup(Hook $hook) + { + $folder = $this->createTempFolder(); + $this->filesystem->copyDirectory($hook->getPath(), $folder); + + $this->tempDirectories[$hook->name] = $folder; + } + + protected function createTempFolder() + { + $path = sys_get_temp_dir().'/hooks-'.uniqid(); + + if ($this->filesystem->exists($path)) { + return $this->createTempFolder(); + } + + return $path; + } + + protected function clearTemponaryFiles() + { + foreach ($this->tempDirectories as $directory) { + $this->filesystem->deleteDirectory($directory); + } + } } // TODO: MOVE! diff --git a/src/HooksServiceProvider.php b/src/HooksServiceProvider.php index 48d408f..73304d4 100644 --- a/src/HooksServiceProvider.php +++ b/src/HooksServiceProvider.php @@ -2,6 +2,7 @@ namespace Larapack\Hooks; +use Illuminate\Filesystem\Filesystem; use Illuminate\Foundation\AliasLoader; use Illuminate\Support\ServiceProvider; @@ -31,7 +32,12 @@ public function register() } // Register Hooks system and aliases - $this->app->singleton(Hooks::class, Hooks::class); + $this->app->singleton(Hooks::class, function ($app) { + $filesystem = $app[Filesystem::class]; + $migrator = $app['migrator']; + + return new Hooks($filesystem, $migrator); + }); $this->app->alias(Hooks::class, 'hooks'); } diff --git a/stub/composer.json b/stub/composer.json index 5b836e0..cbc4930 100644 --- a/stub/composer.json +++ b/stub/composer.json @@ -1,19 +1,34 @@ { - "name": "kebab-case", - "description": "This is my first hook.", - "require": { - "larapack/hooks": "~1.0" - }, - "autoload": { - "psr-4": { - "StudlyCase\\": "src/" + "name": "kebab-case", + "description": "This is my first hook.", + "require": { + "larapack/hooks": "^1.0.5" + }, + "autoload": { + "psr-4": { + "StudlyCase\\": "src/" + } + }, + "extra": { + "hook": { + "providers": [ + "StudlyCase\\StudlyCaseServiceProvider" + ], + "aliases": { + "StudlyCase": "StudlyCase\\StudlyCaseFacade" + }, + "migrations": [ + "resources/database/migrations" + ], + "seeders": [ + "resources/database/seeders" + ], + "unseeders": [ + "resources/database/unseeders" + ], + "assets": { + "resources/assets": "public/vendor/kebab-case" + } + } } - }, - "extra": { - "hook": { - "providers": [ - "StudlyCase\\StudlyCaseServiceProvider" - ] - } - } } \ No newline at end of file diff --git a/stub/resources/assets/scripts/alert.js b/stub/resources/assets/scripts/alert.js new file mode 100644 index 0000000..10c5661 --- /dev/null +++ b/stub/resources/assets/scripts/alert.js @@ -0,0 +1 @@ +alert('This is a sample file!'); diff --git a/stub/resources/database/migrations/.gitkeep b/stub/resources/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/stub/resources/database/migrations/MIGRATION_DATE_TIME_create_snake_case_table.php b/stub/resources/database/migrations/MIGRATION_DATE_TIME_create_snake_case_table.php new file mode 100644 index 0000000..8071914 --- /dev/null +++ b/stub/resources/database/migrations/MIGRATION_DATE_TIME_create_snake_case_table.php @@ -0,0 +1,34 @@ +increments('id'); + + $table->string('name'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('snake_case'); + } +} diff --git a/stub/resources/database/seeders/.gitkeep b/stub/resources/database/seeders/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/stub/resources/database/seeders/StudlyCaseTableSeeder.php b/stub/resources/database/seeders/StudlyCaseTableSeeder.php new file mode 100644 index 0000000..3d538e8 --- /dev/null +++ b/stub/resources/database/seeders/StudlyCaseTableSeeder.php @@ -0,0 +1,26 @@ +count() > 0) { + return; + } + + DB::table('snake_case')->insert([ + ['name' => 'foo'], + ['name' => 'bar'], + ['name' => 'baz'], + ]); + } +} diff --git a/stub/resources/database/unseeders/.gitkeep b/stub/resources/database/unseeders/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/stub/resources/database/unseeders/StudlyCaseTableUnseeder.php b/stub/resources/database/unseeders/StudlyCaseTableUnseeder.php new file mode 100644 index 0000000..417e348 --- /dev/null +++ b/stub/resources/database/unseeders/StudlyCaseTableUnseeder.php @@ -0,0 +1,25 @@ +whereIn('name', ['foo', 'bar', 'baz']) + ->delete(); + } +} diff --git a/stub/src/StudlyCase.php b/stub/src/StudlyCase.php new file mode 100644 index 0000000..89db8e7 --- /dev/null +++ b/stub/src/StudlyCase.php @@ -0,0 +1,8 @@ +format('Y_m_d_His'); // Make hook $this->artisan('hook:make', [ @@ -95,6 +101,61 @@ public function test_making_local_hook() // Check that hook is not yet installed $this->assertCount(0, app('hooks')->hooks()->all()); + + // Check stubs + $this->assertFileExists(base_path('hooks/local-test-hook/composer.json')); + $this->assertEquals( + $filesystem->get(__DIR__.'/fixtures/composer.json'), + $filesystem->get(base_path('hooks/local-test-hook/composer.json')) + ); + + $this->assertFileExists(base_path('hooks/local-test-hook/src/LocalTestHook.php')); + $this->assertEquals( + $filesystem->get(__DIR__.'/fixtures/src/LocalTestHook.php'), + $filesystem->get(base_path('hooks/local-test-hook/src/LocalTestHook.php')) + ); + + $this->assertFileExists(base_path('hooks/local-test-hook/src/LocalTestHookFacade.php')); + $this->assertEquals( + $filesystem->get(__DIR__.'/fixtures/src/LocalTestHookFacade.php'), + $filesystem->get(base_path('hooks/local-test-hook/src/LocalTestHookFacade.php')) + ); + + $this->assertFileExists(base_path('hooks/local-test-hook/src/LocalTestHookServiceProvider.php')); + $this->assertEquals( + $filesystem->get(__DIR__.'/fixtures/src/LocalTestHookServiceProvider.php'), + $filesystem->get(base_path('hooks/local-test-hook/src/LocalTestHookServiceProvider.php')) + ); + + $this->assertFileExists(base_path('hooks/local-test-hook/resources/assets/scripts/alert.js')); + $this->assertEquals( + $filesystem->get(__DIR__.'/fixtures/resources/assets/scripts/alert.js'), + $filesystem->get(base_path('hooks/local-test-hook/resources/assets/scripts/alert.js')) + ); + + $this->assertFileExists(base_path( + 'hooks/local-test-hook/resources/database/migrations/'.$migrationDateTime.'_create_local_test_hook_table.php' + )); + $this->assertEquals( + $filesystem->get(__DIR__.'/fixtures/resources/database/migrations/'.$migrationDateTime.'_create_local_test_hook_table.php'), + $filesystem->get(base_path('hooks/local-test-hook/resources/database/migrations/'.$migrationDateTime.'_create_local_test_hook_table.php')) + ); + + $this->assertFileExists(base_path( + 'hooks/local-test-hook/resources/database/seeders/LocalTestHookTableSeeder.php' + )); + $this->assertEquals( + $filesystem->get(__DIR__.'/fixtures/resources/database/seeders/LocalTestHookTableSeeder.php'), + $filesystem->get(base_path('hooks/local-test-hook/resources/database/seeders/LocalTestHookTableSeeder.php')) + ); + + $this->assertFileExists(base_path( + 'hooks/local-test-hook/resources/database/unseeders/LocalTestHookTableUnseeder.php' + )); + $this->assertEquals( + $filesystem->get(__DIR__.'/fixtures/resources/database/unseeders/LocalTestHookTableUnseeder.php'), + $filesystem->get(base_path('hooks/local-test-hook/resources/database/unseeders/LocalTestHookTableUnseeder.php')) + ); } public function test_installing_local_hook() @@ -114,7 +175,7 @@ public function test_installing_local_hook() $expect = [ 'name' => 'local-test-hook', 'version' => null, - 'description' => 'This is a hook.', + 'description' => 'This is my first hook.', 'enabled' => false, ]; foreach ($expect as $key => $value) { @@ -153,7 +214,7 @@ public function test_enabling_hook() $expect = [ 'name' => 'local-test-hook', 'version' => null, - 'description' => 'This is a hook.', + 'description' => 'This is my first hook.', 'enabled' => false, ]; foreach ($expect as $key => $value) { @@ -172,7 +233,7 @@ public function test_enabling_hook() $expect = [ 'name' => 'local-test-hook', 'version' => null, - 'description' => 'This is a hook.', + 'description' => 'This is my first hook.', 'enabled' => true, ]; foreach ($expect as $key => $value) { @@ -211,7 +272,7 @@ public function test_disabling_hook() $expect = [ 'name' => 'local-test-hook', 'version' => null, - 'description' => 'This is a hook.', + 'description' => 'This is my first hook.', 'enabled' => false, ]; foreach ($expect as $key => $value) { @@ -230,7 +291,7 @@ public function test_disabling_hook() $expect = [ 'name' => 'local-test-hook', 'version' => null, - 'description' => 'This is a hook.', + 'description' => 'This is my first hook.', 'enabled' => true, ]; foreach ($expect as $key => $value) { @@ -249,7 +310,7 @@ public function test_disabling_hook() $expect = [ 'name' => 'local-test-hook', 'version' => null, - 'description' => 'This is a hook.', + 'description' => 'This is my first hook.', 'enabled' => false, ]; foreach ($expect as $key => $value) { @@ -288,7 +349,7 @@ public function test_uninstall_hook() $expect = [ 'name' => 'local-test-hook', 'version' => null, - 'description' => 'This is a hook.', + 'description' => 'This is my first hook.', 'enabled' => false, ]; foreach ($expect as $key => $value) { @@ -336,7 +397,7 @@ public function test_uninstall_hook_without_delete_parameter() $expect = [ 'name' => 'local-test-hook', 'version' => null, - 'description' => 'This is a hook.', + 'description' => 'This is my first hook.', 'enabled' => false, ]; foreach ($expect as $key => $value) { @@ -588,5 +649,584 @@ public function test_updating_updates_dependencies() $this->assertDirectoryExists(base_path('vendor/marktopper/composer-hook-dependency-2')); } - // TODO: Test that if a hook requires another hook, that hook should be loaded as well + public function test_migrating_hook() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + $this->assertMigrationHasNotRan('2018_01_19_000000_create_tests_table'); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertMigrationHasRan('2018_01_19_000000_create_tests_table'); + } + + public function test_installing_without_migrating_hook() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + $this->assertMigrationHasNotRan('2018_01_19_000000_create_tests_table'); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + '--no-migrate' => true, + '--no-seed' => true, + ]); + + $this->assertFalse(Schema::hasTable('tests')); + $this->assertMigrationHasNotRan('2018_01_19_000000_create_tests_table'); + } + + public function test_unmigrating_hook() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + $this->assertMigrationHasNotRan('2018_01_19_000000_create_tests_table'); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertMigrationHasRan('2018_01_19_000000_create_tests_table'); + + // Uninstall hook + $this->artisan('hook:uninstall', [ + 'name' => 'migrating-hook', + ]); + + $this->assertFalse(Schema::hasTable('tests')); + $this->assertMigrationHasNotRan('2018_01_19_000000_create_tests_table'); + } + + public function test_uninstalling_without_unmigrating_hook() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + $this->assertMigrationHasNotRan('2018_01_19_000000_create_tests_table'); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertMigrationHasRan('2018_01_19_000000_create_tests_table'); + + // Uninstall hook + $this->artisan('hook:uninstall', [ + 'name' => 'migrating-hook', + '--no-unmigrate' => true, + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertMigrationHasRan('2018_01_19_000000_create_tests_table'); + $this->assertEquals(0, DB::table('tests')->count()); + } + + public function test_remigrating_hook_on_update() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + $this->assertMigrationHasNotRan('2018_01_19_000000_create_tests_table'); + $this->assertFalse(Schema::hasTable('another_tests')); + $this->assertMigrationHasNotRan('2018_01_19_100000_create_another_tests_table'); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertMigrationHasRan('2018_01_19_000000_create_tests_table'); + + $this->assertFalse(Schema::hasTable('another_tests')); + $this->assertMigrationHasNotRan('2018_01_19_100000_create_another_tests_table'); + + // Install hook + $this->artisan('hook:update', [ + 'name' => 'migrating-hook', + 'version' => 'v2.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertMigrationHasRan('2018_01_19_000000_create_tests_table'); + $this->assertTrue(Schema::hasTable('another_tests')); + $this->assertMigrationHasRan('2018_01_19_100000_create_another_tests_table'); + } + + public function test_updating_without_remigrating_hook() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + $this->assertMigrationHasNotRan('2018_01_19_000000_create_tests_table'); + $this->assertFalse(Schema::hasTable('another_tests')); + $this->assertMigrationHasNotRan('2018_01_19_100000_create_another_tests_table'); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertMigrationHasRan('2018_01_19_000000_create_tests_table'); + + $this->assertFalse(Schema::hasTable('another_tests')); + $this->assertMigrationHasNotRan('2018_01_19_100000_create_another_tests_table'); + + // Install hook + $this->artisan('hook:update', [ + 'name' => 'migrating-hook', + 'version' => 'v2.0.0', + '--no-migrate' => true, + '--no-seed' => true, + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertMigrationHasRan('2018_01_19_000000_create_tests_table'); + $this->assertFalse(Schema::hasTable('another_tests')); + $this->assertMigrationHasNotRan('2018_01_19_100000_create_another_tests_table'); + } + + public function test_seeding_hook() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(3, DB::table('tests')->count()); + } + + public function test_installing_without_seeding_hook() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + '--no-seed' => true, + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(0, DB::table('tests')->count()); + } + + public function test_unseeding_hook() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(3, DB::table('tests')->count()); + + // Uninstall hook + $this->artisan('hook:uninstall', [ + 'name' => 'migrating-hook', + '--no-unmigrate' => true, + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(0, DB::table('tests')->count()); + } + + public function test_uninstalling_without_unseeding() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(3, DB::table('tests')->count()); + + // Uninstall hook + $this->artisan('hook:uninstall', [ + 'name' => 'migrating-hook', + '--no-unmigrate' => true, + '--no-unseed' => true, + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(3, DB::table('tests')->count()); + } + + public function test_reseeding_on_update() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(3, DB::table('tests')->count()); + + // Update hook + $this->artisan('hook:update', [ + 'name' => 'migrating-hook', + 'version' => 'v2.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(3, DB::table('tests')->count()); + $this->assertTrue(Schema::hasTable('another_tests')); + $this->assertEquals(3, DB::table('another_tests')->count()); + } + + public function test_updating_without_reseeding() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse(Schema::hasTable('tests')); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(3, DB::table('tests')->count()); + + // Update hook + $this->artisan('hook:update', [ + 'name' => 'migrating-hook', + 'version' => 'v2.0.0', + '--no-seed' => true, + ]); + + $this->assertTrue(Schema::hasTable('tests')); + $this->assertEquals(3, DB::table('tests')->count()); + $this->assertTrue(Schema::hasTable('another_tests')); + $this->assertEquals(0, DB::table('another_tests')->count()); + } + + public function test_publishing_assets() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + } + + public function test_installing_without_publishing_assets() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + '--no-publish' => true, + ]); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + } + + public function test_unpublishing_assets() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + + // Uninstall hook + $this->artisan('hook:uninstall', [ + 'name' => 'migrating-hook', + ]); + + $this->assertFalse($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + } + + public function test_unpublishing_assets_without_removing_other_files_in_folder() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + + $filesystem->put( + base_path('public/vendor/migration-hook/assets/test.js'), + "alert('This is just a test!');\n" + ); + + // Uninstall hook + $this->artisan('hook:uninstall', [ + 'name' => 'migrating-hook', + ]); + + $this->assertFalse($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/test.js'))); + $this->assertEquals("alert('This is just a test!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/test.js') + )); + } + + public function test_uninstalling_without_unpublishing_assets() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + + // Uninstall hook + $this->artisan('hook:uninstall', [ + 'name' => 'migrating-hook', + '--no-unpublish' => true, + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + } + + public function test_republishing_assets_on_update() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + + // Update hook + $this->artisan('hook:update', [ + 'name' => 'migrating-hook', + 'version' => 'v2.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/another.js'))); + $this->assertEquals("alert('I am still alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + $this->assertEquals("alert('I am another file!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/another.js') + )); + } + + public function test_updating_without_republishing_assets() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + + // Update hook + $this->artisan('hook:update', [ + 'name' => 'migrating-hook', + 'version' => 'v2.0.0', + '--no-publish' => true, + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertFalse($filesystem->exists(base_path('public/vendor/migration-hook/assets/another.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + } + + public function test_republishing_assets_on_update_with_custom_changed_files() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + + $filesystem->put( + base_path('public/vendor/migration-hook/assets/script.js'), + "alert('Am I still alive?');\n" + ); + + // Update hook + $this->artisan('hook:update', [ + 'name' => 'migrating-hook', + 'version' => 'v2.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/another.js'))); + $this->assertEquals("alert('Am I still alive?');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + $this->assertEquals("alert('I am another file!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/another.js') + )); + } + + public function test_force_republishing_assets_on_update_with_custom_changed_files() + { + $filesystem = app(Filesystem::class); + + $this->assertFalse($filesystem->exists(base_path('public/vendor'))); + + // Install hook + $this->artisan('hook:install', [ + 'name' => 'migrating-hook', + 'version' => 'v1.0.0', + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertEquals("alert('I am alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + + $filesystem->put( + base_path('public/vendor/migration-hook/assets/script.js'), + "alert('Am I still alive?');\n" + ); + + // Update hook + $this->artisan('hook:update', [ + 'name' => 'migrating-hook', + 'version' => 'v2.0.0', + '--force' => true, + ]); + + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/script.js'))); + $this->assertTrue($filesystem->exists(base_path('public/vendor/migration-hook/assets/another.js'))); + $this->assertEquals("alert('I am still alive!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/script.js') + )); + $this->assertEquals("alert('I am another file!');\n", $filesystem->get( + base_path('public/vendor/migration-hook/assets/another.js') + )); + } + + protected function assertMigrationHasRan($name) + { + return $this->assertTrue( + DB::table('migrations')->where('migration', $name)->count() == 1 + ); + } + + protected function assertMigrationHasNotRan($name) + { + return $this->assertFalse( + DB::table('migrations')->where('migration', $name)->count() == 1 + ); + } } diff --git a/tests/TestCase.php b/tests/TestCase.php index f8da04f..be5a6cb 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -25,6 +25,9 @@ public function setUp() // Cleanup old hooks before testing $filesystem->deleteDirectory(base_path('hooks')); + // Cleanup published files + $filesystem->deleteDirectory(base_path('public/vendor')); + // Clear old hooks $hook = app(Hooks::class); $hook->readJsonFile(); @@ -57,6 +60,9 @@ public function setUp() // Reload JSON files app(Hooks::class)->readJsonFile(); + + // Migrate + $this->artisan('migrate'); } public function tearDown() diff --git a/tests/fixtures/composer.json b/tests/fixtures/composer.json new file mode 100644 index 0000000..49c36b5 --- /dev/null +++ b/tests/fixtures/composer.json @@ -0,0 +1,34 @@ +{ + "name": "local-test-hook", + "description": "This is my first hook.", + "require": { + "larapack/hooks": "^1.0.5" + }, + "autoload": { + "psr-4": { + "LocalTestHook\\": "src/" + } + }, + "extra": { + "hook": { + "providers": [ + "LocalTestHook\\LocalTestHookServiceProvider" + ], + "aliases": { + "LocalTestHook": "LocalTestHook\\LocalTestHookFacade" + }, + "migrations": [ + "resources/database/migrations" + ], + "seeders": [ + "resources/database/seeders" + ], + "unseeders": [ + "resources/database/unseeders" + ], + "assets": { + "resources/assets": "public/vendor/local-test-hook" + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/resources/assets/scripts/alert.js b/tests/fixtures/resources/assets/scripts/alert.js new file mode 100644 index 0000000..10c5661 --- /dev/null +++ b/tests/fixtures/resources/assets/scripts/alert.js @@ -0,0 +1 @@ +alert('This is a sample file!'); diff --git a/tests/fixtures/resources/database/migrations/.gitkeep b/tests/fixtures/resources/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/resources/database/migrations/2018_01_20_120000_create_local_test_hook_table.php b/tests/fixtures/resources/database/migrations/2018_01_20_120000_create_local_test_hook_table.php new file mode 100644 index 0000000..04c296a --- /dev/null +++ b/tests/fixtures/resources/database/migrations/2018_01_20_120000_create_local_test_hook_table.php @@ -0,0 +1,34 @@ +increments('id'); + + $table->string('name'); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('local_test_hook'); + } +} diff --git a/tests/fixtures/resources/database/seeders/.gitkeep b/tests/fixtures/resources/database/seeders/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/resources/database/seeders/LocalTestHookTableSeeder.php b/tests/fixtures/resources/database/seeders/LocalTestHookTableSeeder.php new file mode 100644 index 0000000..e747550 --- /dev/null +++ b/tests/fixtures/resources/database/seeders/LocalTestHookTableSeeder.php @@ -0,0 +1,26 @@ +count() > 0) { + return; + } + + DB::table('local_test_hook')->insert([ + ['name' => 'foo'], + ['name' => 'bar'], + ['name' => 'baz'], + ]); + } +} diff --git a/tests/fixtures/resources/database/unseeders/.gitkeep b/tests/fixtures/resources/database/unseeders/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/resources/database/unseeders/LocalTestHookTableUnseeder.php b/tests/fixtures/resources/database/unseeders/LocalTestHookTableUnseeder.php new file mode 100644 index 0000000..d0fe7dc --- /dev/null +++ b/tests/fixtures/resources/database/unseeders/LocalTestHookTableUnseeder.php @@ -0,0 +1,25 @@ +whereIn('name', ['foo', 'bar', 'baz']) + ->delete(); + } +} diff --git a/tests/fixtures/src/LocalTestHook.php b/tests/fixtures/src/LocalTestHook.php new file mode 100644 index 0000000..8467d53 --- /dev/null +++ b/tests/fixtures/src/LocalTestHook.php @@ -0,0 +1,8 @@ +