Explorar o código

:construction: ExtensionMakeCommand

jqh %!s(int64=5) %!d(string=hai) anos
pai
achega
4d10f60964
Modificáronse 31 ficheiros con 775 adicións e 715 borrados
  1. 1 1
      resources/assets/dcat/extra/action.js
  2. 1 1
      resources/assets/dcat/js/extensions/Ajax.js
  3. 1 1
      resources/assets/dcat/js/extensions/SweetAlert2.js
  4. 1 0
      resources/lang/zh-CN/global.php
  5. 1 1
      resources/views/grid/displayer/extensions/description.blade.php
  6. 5 4
      resources/views/grid/displayer/extensions/name.blade.php
  7. 1 9
      resources/views/grid/quick-create/form.blade.php
  8. 1 1
      src/AdminServiceProvider.php
  9. 0 303
      src/Console/ExtendCommand.php
  10. 403 0
      src/Console/ExtensionMakeCommand.php
  11. 0 20
      src/Console/stubs/extension/LICENSE.stub
  12. 1 2
      src/Console/stubs/extension/README.md.stub
  13. 0 4
      src/Console/stubs/extension/bootstrap.stub
  14. 14 9
      src/Console/stubs/extension/composer.json.stub
  15. 4 3
      src/Console/stubs/extension/controller.stub
  16. 4 0
      src/Console/stubs/extension/css.stub
  17. 26 20
      src/Console/stubs/extension/extension.stub
  18. 26 0
      src/Console/stubs/extension/js.stub
  19. 2 2
      src/Console/stubs/extension/routes.stub
  20. 0 41
      src/Console/stubs/extension/service-provider.stub
  21. 14 0
      src/Console/stubs/extension/setting.stub
  22. 7 0
      src/Console/stubs/extension/version.stub
  23. 13 1
      src/Console/stubs/extension/view.stub
  24. 91 19
      src/Extend/Manager.php
  25. 84 228
      src/Extend/ServiceProvider.php
  26. 1 1
      src/Http/Actions/Extensions/Disable.php
  27. 1 1
      src/Http/Actions/Extensions/Enable.php
  28. 1 1
      src/Http/Actions/Extensions/Update.php
  29. 23 16
      src/Http/Controllers/ExtensionController.php
  30. 36 26
      src/Http/Forms/InstallFromLocal.php
  31. 12 0
      src/Http/JsonResponse.php

+ 1 - 1
resources/assets/dcat/extra/action.js

@@ -83,7 +83,7 @@
                 if (request && typeof request.responseJSON === 'object') {
                     Dcat.error(request.responseJSON.message)
                 }
-                console.error(request);
+                console.error(result);
             }
         }
 

+ 1 - 1
resources/assets/dcat/js/extensions/Ajax.js

@@ -153,7 +153,7 @@ export default class Ajax {
             if (data.alert) {
                 Dcat.swal[data.type](message, data.detail);
             } else {
-                Dcat[data.type](message);
+                Dcat[data.type](message, null, data.timeout ? {timeOut: data.timeout*1000} : {});
             }
         }
 

+ 1 - 1
resources/assets/dcat/js/extensions/SweetAlert2.js

@@ -57,8 +57,8 @@ export default class SweetAlert2 {
     fire(title, message, type, options) {
         options = $.extend({
             title: title,
-            text: message,
             type: type,
+            html: message,
         }, options);
 
         return this.swal.fire(options);

+ 1 - 0
resources/lang/zh-CN/global.php

@@ -21,6 +21,7 @@ return [
         'roles'                 => '角色',
         'path'                  => '路径',
         'input'                 => '输入',
+        'type'                  => '类型',
     ],
     'labels' => [
         'list'     => '列表',

+ 1 - 1
resources/views/grid/displayer/extensions/description.blade.php

@@ -5,7 +5,7 @@
 @else
     {!! $updateAction !!}
 @endif
- | 
+  | 
 
 @if($settingAction)
     {!! $settingAction !!}

+ 5 - 4
resources/views/grid/displayer/extensions/name.blade.php

@@ -1,9 +1,10 @@
 <span class="ext-name">
     {{ $value }}
-    @if($row->homepage)
-        <a href='{!! $row->homepage !!}' target='_blank' class="feather icon-chrome"></a>
-    @endif
 </span>
+@if($row->homepage)
+    <a href='{!! $row->homepage !!}' target='_blank' class="feather icon-chrome"></a>
+@endif
+
 <div style="height: 10px"></div>
 
 @if($row->version)
@@ -28,6 +29,6 @@
         display: inline;
     }
     .ext-name {
-        font-size: 1.1rem;
+        font-size: 1.15rem;
     }
 </style>

+ 1 - 9
resources/views/grid/quick-create/form.blade.php

@@ -62,15 +62,7 @@
                 ctr.attr('submitting', '');
                 btn.buttonLoading(false);
 
-                if (data.status == true) {
-                    Dcat.success(data.message);
-                    Dcat.reload();
-                    return;
-                }
-
-                if (typeof data.validation !== 'undefined') {
-                    Dcat.warning(data.message)
-                }
+                Dcat.handleJsonResponse(data);
             },
             error:function(xhq){
                 btn.buttonLoading(false);

+ 1 - 1
src/AdminServiceProvider.php

@@ -30,7 +30,6 @@ class AdminServiceProvider extends ServiceProvider
         Console\UninstallCommand::class,
         Console\CreateUserCommand::class,
         Console\ResetPasswordCommand::class,
-        Console\ExtendCommand::class,
         Console\ExportSeedCommand::class,
         Console\IdeHelperCommand::class,
         Console\FormCommand::class,
@@ -38,6 +37,7 @@ class AdminServiceProvider extends ServiceProvider
         Console\MenuCacheCommand::class,
         Console\MinifyCommand::class,
         Console\AppCommand::class,
+        Console\ExtensionMakeCommand::class,
         Console\ExtensionInstallCommand::class,
         Console\ExtensionUninstallCommand::class,
         Console\ExtensionRefreshCommand::class,

+ 0 - 303
src/Console/ExtendCommand.php

@@ -1,303 +0,0 @@
-<?php
-
-namespace Dcat\Admin\Console;
-
-use Dcat\Admin\Support\Helper;
-use Illuminate\Console\Command;
-use Illuminate\Filesystem\Filesystem;
-use Illuminate\Support\Str;
-
-class ExtendCommand extends Command
-{
-    /**
-     * The console command name.
-     *
-     * @var string
-     */
-    protected $signature = 'admin:extend {extension} {--namespace=}';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = 'Build a dcat-admin extension';
-
-    /**
-     * @var string
-     */
-    protected $basePath = '';
-
-    /**
-     * @var Filesystem
-     */
-    protected $filesystem;
-
-    /**
-     * @var string
-     */
-    protected $namespace;
-
-    /**
-     * @var string
-     */
-    protected $className;
-
-    /**
-     * @var string
-     */
-    protected $package;
-
-    /**
-     * @var string
-     */
-    protected $extensionDir;
-
-    /**
-     * @var array
-     */
-    protected $dirs = [
-        'database/migrations',
-        'database/seeds',
-        'resources/assets',
-        'resources/views',
-        'src/Http/Controllers',
-        'routes',
-    ];
-
-    /**
-     * Execute the console command.
-     *
-     * @return void
-     */
-    public function handle(Filesystem $filesystem)
-    {
-        $this->filesystem = $filesystem;
-
-        $this->extensionDir = config('admin.extension_dir') ?: app_path('Admin/Extensions');
-
-        if (! file_exists($this->extensionDir)) {
-            $this->makeDir();
-        }
-
-        $this->package = $this->argument('extension');
-
-        InputExtensionName :
-        if (! Helper::validateExtensionName($this->package)) {
-            $this->package = $this->ask("[$this->package] is not a valid package name, please input a name like (<vendor>/<name>)");
-            goto InputExtensionName;
-        }
-
-        $this->makeDirs();
-        $this->makeFiles();
-
-        $this->info("The extension scaffolding generated successfully. \r\n");
-        $this->showTree();
-    }
-
-    /**
-     * Show extension scaffolding with tree structure.
-     */
-    protected function showTree()
-    {
-        $tree = <<<TREE
-{$this->extensionPath()}
-    ├── LICENSE
-    ├── README.md
-    ├── composer.json
-    ├── database
-    │   ├── migrations
-    │   └── seeds
-    ├── resources
-    │   ├── assets
-    │   └── views
-    │       └── index.blade.php
-    ├── routes
-    │   └── web.php
-    └── src
-        ├── {$this->className}.php
-        ├── bootstrap.php
-        └── Http
-            └── Controllers
-                └── {$this->className}Controller.php
-TREE;
-
-        $this->info($tree);
-    }
-
-    /**
-     * Make extension files.
-     */
-    protected function makeFiles()
-    {
-        $this->namespace = $this->getRootNameSpace();
-
-        $this->className = $this->getClassName();
-
-        // copy files
-        $this->copy([
-            __DIR__.'/stubs/extension/view.stub'       => 'resources/views/index.blade.php',
-            __DIR__.'/stubs/extension/.gitignore.stub' => '.gitignore',
-            __DIR__.'/stubs/extension/README.md.stub'  => 'README.md',
-            __DIR__.'/stubs/extension/LICENSE.stub'    => 'LICENSE',
-        ]);
-
-        // make composer.json
-        $composerContents = str_replace(
-            [':package', ':namespace', ':class_name'],
-            [$this->package, str_replace('\\', '\\\\', $this->namespace).'\\\\', $this->className],
-            file_get_contents(__DIR__.'/stubs/extension/composer.json.stub')
-        );
-        $this->putFile('composer.json', $composerContents);
-
-        $basePackage = Helper::slug(basename($this->package));
-
-        // make class
-        $classContents = str_replace(
-            [':namespace', ':class_name', ':title', ':path', ':base_package'],
-            [$this->namespace, $this->className, Str::title($this->className), $basePackage, $basePackage],
-            file_get_contents(__DIR__.'/stubs/extension/extension.stub')
-        );
-        $this->putFile("src/{$this->className}.php", $classContents);
-
-        // make bootstrap
-        $bootstrap = str_replace(
-            [':namespace', ':class_name'],
-            [$this->namespace, $this->className],
-            file_get_contents(__DIR__.'/stubs/extension/bootstrap.stub')
-        );
-        $this->putFile('src/bootstrap.php', $bootstrap);
-
-        // make service provider
-        $providerContents = str_replace(
-            [':namespace', ':class_name', ':base_package', ':package'],
-            [$this->namespace, $this->className, $basePackage, $this->package],
-            file_get_contents(__DIR__.'/stubs/extension/service-provider.stub')
-        );
-        $this->putFile("src/{$this->className}ServiceProvider.php", $providerContents);
-
-        // make controller
-        $controllerContent = str_replace(
-            [':namespace', ':class_name', ':base_package'],
-            [$this->namespace, $this->className, $basePackage],
-            file_get_contents(__DIR__.'/stubs/extension/controller.stub')
-        );
-        $this->putFile("src/Http/Controllers/{$this->className}Controller.php", $controllerContent);
-
-        // make routes
-        $routesContent = str_replace(
-            [':namespace', ':class_name', ':path'],
-            [$this->namespace, $this->className, $basePackage],
-            file_get_contents(__DIR__.'/stubs/extension/routes.stub')
-        );
-        $this->putFile('routes/web.php', $routesContent);
-    }
-
-    /**
-     * Get root namespace for this package.
-     *
-     * @return array|null|string
-     */
-    protected function getRootNameSpace()
-    {
-        if (! $namespace = $this->option('namespace')) {
-            [$vendor, $name] = explode('/', $this->package);
-
-            $default = str_replace(['-', '-'], '', Str::title($vendor).'\\'.Str::title($name));
-
-            $namespace = $this->ask('Root namespace', $default);
-        }
-
-        return $namespace;
-    }
-
-    /**
-     * Get extension class name.
-     *
-     * @return string
-     */
-    protected function getClassName()
-    {
-        return ucfirst(Str::camel(basename($this->package)));
-    }
-
-    /**
-     * Create package dirs.
-     */
-    protected function makeDirs()
-    {
-        $this->basePath = rtrim($this->extensionDir, '/').'/'.ltrim($this->package, '/');
-
-        $this->makeDir($this->dirs);
-    }
-
-    /**
-     * Extension path.
-     *
-     * @param string $path
-     *
-     * @return string
-     */
-    protected function extensionPath($path = '')
-    {
-        $path = rtrim($path, '/');
-
-        if (empty($path)) {
-            return rtrim($this->basePath, '/');
-        }
-
-        return rtrim($this->basePath, '/').'/'.ltrim($path, '/');
-    }
-
-    /**
-     * Put contents to file.
-     *
-     * @param string $to
-     * @param string $content
-     */
-    protected function putFile($to, $content)
-    {
-        $to = $this->extensionPath($to);
-
-        $this->filesystem->put($to, $content);
-    }
-
-    /**
-     * Copy files to extension path.
-     *
-     * @param string|array $from
-     * @param string|null  $to
-     */
-    protected function copy($from, $to = null)
-    {
-        if (is_array($from) && is_null($to)) {
-            foreach ($from as $key => $value) {
-                $this->copy($key, $value);
-            }
-
-            return;
-        }
-
-        if (! file_exists($from)) {
-            return;
-        }
-
-        $to = $this->extensionPath($to);
-
-        $this->filesystem->copy($from, $to);
-    }
-
-    /**
-     * Make new directory.
-     *
-     * @param array|string $paths
-     */
-    protected function makeDir($paths = '')
-    {
-        foreach ((array) $paths as $path) {
-            $path = $this->extensionPath($path);
-
-            $this->filesystem->makeDirectory($path, 0755, true, true);
-        }
-    }
-}

+ 403 - 0
src/Console/ExtensionMakeCommand.php

@@ -0,0 +1,403 @@
+<?php
+
+namespace Dcat\Admin\Console;
+
+use Dcat\Admin\Support\Helper;
+use Illuminate\Console\Command;
+use Illuminate\Filesystem\Filesystem;
+use Illuminate\Support\Str;
+
+class ExtensionMakeCommand extends Command
+{
+    /**
+     * The console command name.
+     *
+     * @var string
+     */
+    protected $signature = 'admin:ext-make 
+    {name : The name of the extension. Eg: author-name/extension-name} 
+    {--namespace= : The namespace of the extension.}
+    {--theme}
+    ';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Build a dcat-admin extension';
+
+    /**
+     * @var string
+     */
+    protected $basePath = '';
+
+    /**
+     * @var Filesystem
+     */
+    protected $filesystem;
+
+    /**
+     * @var string
+     */
+    protected $namespace;
+
+    /**
+     * @var string
+     */
+    protected $className;
+
+    /**
+     * @var string
+     */
+    protected $extensionName;
+
+    /**
+     * @var string
+     */
+    protected $package;
+
+    /**
+     * @var string
+     */
+    protected $extensionDir;
+
+    /**
+     * @var array
+     */
+    protected $dirs = [
+        'updates',
+        'resources/assets/css',
+        'resources/assets/js',
+        'resources/views',
+        'resources/lang',
+        'src/Models',
+        'src/Form',
+        'src/Grid',
+        'src/Show',
+        'src/Http/Controllers',
+        'src/Http/Middleware',
+    ];
+
+    protected $themeDirs = [
+        'updates',
+        'resources/assets/css',
+        'resources/views',
+        'src',
+    ];
+
+    /**
+     * Execute the console command.
+     *
+     * @return void
+     */
+    public function handle(Filesystem $filesystem)
+    {
+        $this->filesystem = $filesystem;
+
+        $this->extensionDir = admin_extension_path();
+
+        if (! file_exists($this->extensionDir)) {
+            $this->makeDir();
+        }
+
+        $this->package = str_replace('.', '/', $this->argument('name'));
+        $this->extensionName = str_replace('/', '.', $this->package);
+
+        $this->basePath = rtrim($this->extensionDir, '/').'/'.ltrim($this->package, '/');
+
+        if (is_dir($this->basePath)) {
+            return $this->error(sprintf('The extension [%s] already exists!', $this->package));
+        }
+
+        InputExtensionName :
+        if (! Helper::validateExtensionName($this->package)) {
+            $this->package = $this->ask("[$this->package] is not a valid package name, please input a name like (<vendor>/<name>)");
+            goto InputExtensionName;
+        }
+
+        $this->makeDirs();
+        $this->makeFiles();
+
+        $this->info("The extension scaffolding generated successfully. \r\n");
+        $this->showTree();
+    }
+
+    /**
+     * Show extension scaffolding with tree structure.
+     */
+    protected function showTree()
+    {
+        if ($this->option('theme')) {
+            $tree = <<<TREE
+{$this->extensionPath()}
+    ├── README.md
+    ├── composer.json
+    ├── version.php
+    ├── updates
+    ├── resources
+    │   ├── lang
+    │   ├── assets
+    │   │   └── css
+    │   │       └── index.css
+    │   └── views
+    └── src
+        ├── {$this->className}ServiceProvider.php
+        └── Setting.php
+TREE;
+        } else {
+            $tree = <<<TREE
+{$this->extensionPath()}
+    ├── README.md
+    ├── composer.json
+    ├── version.php
+    ├── updates
+    ├── resources
+    │   ├── lang
+    │   ├── assets
+    │   │   ├── css
+    │   │   │   └── index.css
+    │   │   └── js
+    │   │       └── index.js
+    │   └── views
+    │       └── index.blade.php
+    └── src
+        ├── {$this->className}ServiceProvider.php
+        ├── Setting.php
+        ├── Models
+        ├── Grid
+        ├── Form
+        ├── Show
+        └── Http
+            ├── routes.php
+            ├── Middleware
+            └── Controllers
+                └── {$this->className}Controller.php
+TREE;
+        }
+
+        $this->info($tree);
+    }
+
+    /**
+     * Make extension files.
+     */
+    protected function makeFiles()
+    {
+        $this->namespace = $this->getRootNameSpace();
+
+        $this->className = $this->getClassName();
+
+        // copy files
+        $this->copyFiles();
+
+        // make composer.json
+        $composerContents = str_replace(
+            ['{package}', '{namespace}', '{className}'],
+            [$this->package, str_replace('\\', '\\\\', $this->namespace).'\\\\', $this->className],
+            file_get_contents(__DIR__.'/stubs/extension/composer.json.stub')
+        );
+        $this->putFile('composer.json', $composerContents);
+
+        // make composer.json
+        $settingContents = str_replace(
+            ['{namespace}'],
+            [$this->namespace],
+            file_get_contents(__DIR__.'/stubs/extension/setting.stub')
+        );
+        $this->putFile('src/Setting.php', $settingContents);
+
+        $basePackage = Helper::slug(basename($this->package));
+
+        // make class
+        $classContents = str_replace(
+            ['{namespace}', '{className}', '{title}', '{path}', '{basePackage}', '{property}', '{registerTheme}'],
+            [
+                $this->namespace,
+                $this->className,
+                Str::title($this->className),
+                $basePackage,
+                $basePackage,
+                $this->makeProviderContent(),
+                $this->makeRegisterThemeContent(),
+            ],
+            file_get_contents(__DIR__.'/stubs/extension/extension.stub')
+        );
+        $this->putFile("src/{$this->className}ServiceProvider.php", $classContents);
+
+        if (! $this->option('theme')) {
+            // make controller
+            $controllerContent = str_replace(
+                ['{namespace}', '{className}', '{name}'],
+                [$this->namespace, $this->className, $this->extensionName],
+                file_get_contents(__DIR__.'/stubs/extension/controller.stub')
+            );
+            $this->putFile("src/Http/Controllers/{$this->className}Controller.php", $controllerContent);
+
+            $viewContents = str_replace(
+                ['{name}'],
+                [$this->extensionName],
+                file_get_contents(__DIR__.'/stubs/extension/view.stub')
+            );
+            $this->putFile('resources/views/index.blade.php', $viewContents);
+
+            // make routes
+            $routesContent = str_replace(
+                ['{namespace}', '{className}', '{path}'],
+                [$this->namespace, $this->className, $basePackage],
+                file_get_contents(__DIR__.'/stubs/extension/routes.stub')
+            );
+            $this->putFile('src/Http/routes.php', $routesContent);
+        }
+    }
+
+    protected function makeProviderContent()
+    {
+        if (! $this->option('theme')) {
+            return <<<'TEXT'
+protected $js = [
+        'js/index.js',
+    ];
+TEXT;
+        }
+        return <<<'TEXT'
+protected $type = self::TYPE_THEME;
+
+TEXT;
+    }
+
+    protected function makeRegisterThemeContent()
+    {
+        if (! $this->option('theme')) {
+            return;
+        }
+
+        return <<<'TEXT'
+Admin::baseCss($this->formatAssetFiles($this->css));
+TEXT;
+    }
+
+    protected function copyFiles()
+    {
+        $files = [
+            $view = __DIR__.'/stubs/extension/view.stub' => 'resources/views/index.blade.php',
+            $js = __DIR__.'/stubs/extension/js.stub'     => 'resources/assets/js/index.js',
+            __DIR__.'/stubs/extension/css.stub'          => 'resources/assets/css/index.css',
+            __DIR__.'/stubs/extension/.gitignore.stub'   => '.gitignore',
+            __DIR__.'/stubs/extension/README.md.stub'    => 'README.md',
+            __DIR__.'/stubs/extension/version.stub'      => 'version.php',
+        ];
+
+        if ($this->option('theme')) {
+            unset($files[$view], $files[$js]);
+        }
+
+        $this->copy($files);
+    }
+
+    /**
+     * Get root namespace for this package.
+     *
+     * @return array|null|string
+     */
+    protected function getRootNameSpace()
+    {
+        [$vendor, $name] = explode('/', $this->package);
+
+        $default = str_replace(['-'], '', Str::title($vendor).'\\'.Str::title($name));
+
+        if (! $namespace = $this->option('namespace')) {
+            $namespace = $this->ask('Root namespace', $default);
+        }
+
+        return $namespace === 'default' ? $default : $namespace;
+    }
+
+    /**
+     * Get extension class name.
+     *
+     * @return string
+     */
+    protected function getClassName()
+    {
+        return ucfirst(Str::camel(basename($this->package)));
+    }
+
+    /**
+     * Create package dirs.
+     */
+    protected function makeDirs()
+    {
+        $this->makeDir($this->option('theme') ? $this->themeDirs : $this->dirs);
+    }
+
+    /**
+     * Extension path.
+     *
+     * @param string $path
+     *
+     * @return string
+     */
+    protected function extensionPath($path = '')
+    {
+        $path = rtrim($path, '/');
+
+        if (empty($path)) {
+            return rtrim($this->basePath, '/');
+        }
+
+        return rtrim($this->basePath, '/').'/'.ltrim($path, '/');
+    }
+
+    /**
+     * Put contents to file.
+     *
+     * @param string $to
+     * @param string $content
+     */
+    protected function putFile($to, $content)
+    {
+        $to = $this->extensionPath($to);
+
+        $this->filesystem->put($to, $content);
+    }
+
+    /**
+     * Copy files to extension path.
+     *
+     * @param string|array $from
+     * @param string|null  $to
+     */
+    protected function copy($from, $to = null)
+    {
+        if (is_array($from) && is_null($to)) {
+            foreach ($from as $key => $value) {
+                $this->copy($key, $value);
+            }
+
+            return;
+        }
+
+        if (! file_exists($from)) {
+            return;
+        }
+
+        $to = $this->extensionPath($to);
+
+        $this->filesystem->copy($from, $to);
+    }
+
+    /**
+     * Make new directory.
+     *
+     * @param array|string $paths
+     */
+    protected function makeDir($paths = '')
+    {
+        foreach ((array) $paths as $path) {
+            $path = $this->extensionPath($path);
+
+            $this->filesystem->makeDirectory($path, 0755, true, true);
+        }
+    }
+}

+ 0 - 20
src/Console/stubs/extension/LICENSE.stub

@@ -1,20 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2015 jqh
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 1 - 2
src/Console/stubs/extension/README.md.stub

@@ -1,4 +1,3 @@
-dcat-admin extension
-======
+# Dcat Admin Extension
 
 

+ 0 - 4
src/Console/stubs/extension/bootstrap.stub

@@ -1,4 +0,0 @@
-<?php
-
-// Register the extension.
-Dcat\Admin\Admin::extend(:namespace\:class_name::class);

+ 14 - 9
src/Console/stubs/extension/composer.json.stub

@@ -1,9 +1,9 @@
 {
-    "name": ":package",
-    "description": "description...",
+    "name": "{package}",
+    "description": "Description...",
     "type": "library",
     "keywords": ["dcat-admin", "extension"],
-    "homepage": "https://github.com/:package",
+    "homepage": "https://github.com/{package}",
     "license": "MIT",
     "authors": [
         {
@@ -13,14 +13,19 @@
     ],
     "require": {
         "php": ">=7.1.0",
-        "dcat/laravel-admin": "*"
+        "dcat/laravel-admin": "^2.*"
     },
     "autoload": {
         "psr-4": {
-            ":namespace": "src/"
-        },
-         "files": [
-            "src/bootstrap.php"
-        ]
+            "{namespace}": "src/"
+        }
+    },
+    "extra": {
+        "dcat-admin": "{namespace}{className}ServiceProvider",
+        "laravel": {
+            "providers": [
+                "{namespace}{className}ServiceProvider"
+            ]
+        }
     }
 }

+ 4 - 3
src/Console/stubs/extension/controller.stub

@@ -1,17 +1,18 @@
 <?php
 
-namespace :namespace\Http\Controllers;
+namespace {namespace}\Http\Controllers;
 
 use Dcat\Admin\Layout\Content;
+use Dcat\Admin\Admin;
 use Illuminate\Routing\Controller;
 
-class :class_nameController extends Controller
+class {className}Controller extends Controller
 {
     public function index(Content $content)
     {
         return $content
             ->title('Title')
             ->description('Description')
-            ->body(view(':base_package::index'));
+            ->body(Admin::view('{name}::index'));
     }
 }

+ 4 - 0
src/Console/stubs/extension/css.stub

@@ -0,0 +1,4 @@
+.extension-demo {
+    font-size: 1.3rem;
+    cursor: pointer;
+}

+ 26 - 20
src/Console/stubs/extension/extension.stub

@@ -1,26 +1,32 @@
 <?php
 
-namespace :namespace;
+namespace {namespace};
 
-use Dcat\Admin\Extension;
+use Dcat\Admin\Extend\ServiceProvider;
+use Dcat\Admin\Admin;
 
-class :class_name extends Extension
+class {className}ServiceProvider extends ServiceProvider
 {
-    const NAME = ':base_package';
-
-    protected $serviceProvider = :class_nameServiceProvider::class;
-
-    protected $composer = __DIR__.'/../composer.json';
-
-    protected $assets = __DIR__.'/../resources/assets';
-
-    protected $views = __DIR__.'/../resources/views';
-
-//    protected $lang = __DIR__.'/../resources/lang';
-
-    protected $menu = [
-        'title' => ':title',
-        'path'  => ':path',
-        'icon'  => '',
-    ];
+	{property}
+	protected $css = [
+		'css/index.css',
+	];
+
+	public function register()
+	{
+		//
+	}
+
+	public function init()
+	{
+		parent::init();
+
+		//
+		{registerTheme}
+	}
+
+	public function settingForm()
+	{
+		return new Setting($this);
+	}
 }

+ 26 - 0
src/Console/stubs/extension/js.stub

@@ -0,0 +1,26 @@
+(function (w, $) {
+    function ExtensionDemo(options) {
+        this.options = $.extend({
+            $el: $('.demo'),
+        }, options);
+
+        this.init(this.options);
+    }
+
+    ExtensionDemo.prototype = {
+        init: function (options) {
+            options.$el.on('click', function () {
+                Dcat.success($(this).text());
+            });
+
+            console.log('Done.');
+        },
+    };
+
+    $.fn.extensionDemo = function (options) {
+        options = options || {};
+        options.$el = $(this);
+
+        return new ExtensionDemo(options);
+    };
+})(window, jQuery);

+ 2 - 2
src/Console/stubs/extension/routes.stub

@@ -1,5 +1,5 @@
 <?php
 
-use :namespace\Http\Controllers;
+use {namespace}\Http\Controllers;
 
-Route::get(':path', Controllers\:class_nameController::class.'@index');
+Route::get('{path}', Controllers\{className}Controller::class.'@index');

+ 0 - 41
src/Console/stubs/extension/service-provider.stub

@@ -1,41 +0,0 @@
-<?php
-
-namespace :namespace;
-
-use Illuminate\Support\ServiceProvider;
-
-class :class_nameServiceProvider extends ServiceProvider
-{
-    /**
-     * {@inheritdoc}
-     */
-    public function boot()
-    {
-        $extension = :class_name::make();
-
-        if ($views = $extension->views()) {
-            $this->loadViewsFrom($views, :class_name::NAME);
-        }
-
-        if ($lang = $extension->lang()) {
-            $this->loadTranslationsFrom($lang, :class_name::NAME);
-        }
-
-        if ($migrations = $extension->migrations()) {
-            $this->loadMigrationsFrom($migrations);
-        }
-
-        $this->app->booted(function () use ($extension) {
-            $extension->routes(__DIR__.'/../routes/web.php');
-        });
-    }
-
-    /**
-     * Register the service provider.
-     *
-     * @return void
-     */
-    public function register()
-    {
-    }
-}

+ 14 - 0
src/Console/stubs/extension/setting.stub

@@ -0,0 +1,14 @@
+<?php
+
+namespace {namespace};
+
+use Dcat\Admin\Extend\Setting as Form;
+
+class Setting extends Form
+{
+    public function form()
+    {
+        $this->text('key1')->required();
+        $this->text('key2')->required();
+    }
+}

+ 7 - 0
src/Console/stubs/extension/version.stub

@@ -0,0 +1,7 @@
+<?php
+
+return [
+    '1.0.0' => [
+        'Initialize extension.',
+    ],
+];

+ 13 - 1
src/Console/stubs/extension/view.stub

@@ -1 +1,13 @@
-Welcome to dcat-admin
+<div class="extension-demo">
+	Welcome to dcat-admin !
+</div>
+
+<style>
+	.extension-demo {
+		color: @primary;
+	}
+</style>
+
+<script require="@{name}">
+	$('.extension-demo').extensionDemo();
+</script>

+ 91 - 19
src/Extend/Manager.php

@@ -8,10 +8,12 @@ use Dcat\Admin\Exception\RuntimeException;
 use Dcat\Admin\Models\Extension as ExtensionModel;
 use Dcat\Admin\Models\Extension;
 use Dcat\Admin\Support\Composer;
+use Dcat\Admin\Support\Helper;
 use Dcat\Admin\Support\Zip;
 use Illuminate\Contracts\Container\Container;
 use Illuminate\Filesystem\Filesystem;
 use Illuminate\Support\Collection;
+use Illuminate\Support\Str;
 use RecursiveDirectoryIterator;
 use RecursiveIteratorIterator;
 
@@ -40,9 +42,9 @@ class Manager
     protected $settings;
 
     /**
-     * @var string
+     * @var Filesystem
      */
-    protected $tempDirectory;
+    protected $files;
 
     public function __construct(Container $app)
     {
@@ -50,11 +52,7 @@ class Manager
 
         $this->extensions = new Collection();
 
-        $this->tempDirectory = storage_path('extensions');
-
-        if (! is_dir($this->tempDirectory)) {
-            app('files')->makeDirectory($this->tempDirectory, 0777, true);
-        }
+        $this->files = app('files');
     }
 
     /**
@@ -74,11 +72,7 @@ class Manager
      */
     public function boot()
     {
-        foreach ($this->extensions as $extension) {
-            if ($this->enabled($extension->getName())) {
-                $extension->boot();
-            }
-        }
+        $this->extensions->each->boot();
     }
 
     /**
@@ -124,7 +118,11 @@ class Manager
     public function load()
     {
         foreach ($this->getExtensionDirectories() as $directory) {
-            $this->loadExtension($directory);
+            try {
+                $this->loadExtension($directory);
+            } catch (\Throwable $e) {
+                $this->reportException($e);
+            }
         }
     }
 
@@ -244,7 +242,7 @@ class Manager
     {
         $composerProperty = Composer::parse($directory.'/composer.json');
 
-        $serviceProvider = $composerProperty->get('dcat-admin.provider');
+        $serviceProvider = $composerProperty->get('extra.dcat-admin');
         $psr4 = $composerProperty->get('autoload.psr-4');
 
         if (! $serviceProvider || ! $psr4) {
@@ -263,13 +261,15 @@ class Manager
     /**
      * 获取扩展目录.
      *
+     * @param string $dirPath
+     *
      * @return array
      */
-    public function getExtensionDirectories()
+    public function getExtensionDirectories($dirPath = null)
     {
         $extensions = [];
 
-        $dirPath = admin_extension_path();
+        $dirPath = $dirPath ?: admin_extension_path();
 
         if (! is_dir($dirPath)) {
             return $extensions;
@@ -299,6 +299,16 @@ class Manager
      */
     public function addExtension(ServiceProvider $serviceProvider)
     {
+        if (! $serviceProvider->getName()) {
+            $json = dirname(Helper::guessClassFileName($serviceProvider)).'/composer.json';
+
+            if (! is_file($json)) {
+                throw new RuntimeException('Error extension "%s"', get_class($serviceProvider));
+            }
+
+            $serviceProvider->withComposerProperty(Composer::parse($json));
+        }
+
         $this->extensions->put($serviceProvider->getName(), $serviceProvider);
 
         $this->app->instance($abstract = get_class($serviceProvider), $serviceProvider);
@@ -328,6 +338,10 @@ class Manager
     {
         $filePath = is_file($filePath) ? $filePath : $this->getFilePath($filePath);
 
+        if (! $this->checkZip($filePath)) {
+            throw new RuntimeException(sprintf('Error extension file "%s".', $filePath));
+        }
+
         if (! Zip::extract($filePath, admin_extension_path())) {
             throw new AdminException(sprintf('Unable to extract core file \'%s\'.', $filePath));
         }
@@ -336,7 +350,34 @@ class Manager
     }
 
     /**
-     * Calculates a file path for a file code
+     * 验证文件是否正确.
+     *
+     * @param string $filePath
+     *
+     * @return bool
+     */
+    public function checkZip($filePath)
+    {
+        // 创建临时目录.
+        $tempPath = $this->makeTempDirectory();
+
+        try {
+            $filePath = is_file($filePath) ? $filePath : $this->getFilePath($filePath);
+
+            if (! Zip::extract($filePath, $tempPath)) {
+                throw new AdminException(sprintf('Unable to extract core file \'%s\'.', $filePath));
+            }
+
+            $extensions = $this->getExtensionDirectories($tempPath);
+        } finally {
+            $this->files->deleteDirectory($tempPath);
+        }
+
+        return count($extensions) === 1;
+    }
+
+    /**
+     * 生成临时文件.
      *
      * @param string $fileCode A unique file code
      * @return string           Full path on the disk
@@ -345,7 +386,7 @@ class Manager
     {
         $name = md5($fileCode).'.arc';
 
-        return $this->tempDirectory.'/'.$name;
+        return $this->makeTempDirectory('extensions').'/'.$name;
     }
 
     /**
@@ -384,6 +425,32 @@ class Manager
         return app('admin.extend.version');
     }
 
+    /**
+     * 创建临时目录.
+     *
+     * @param string $dir
+     *
+     * @return string
+     */
+    protected function makeTempDirectory($dir = null)
+    {
+        $tempDir = storage_path('tmp/'.($dir ?: time().Str::random()));
+
+        if (! is_dir($tempDir)) {
+            if (! $this->files->makeDirectory($tempDir, 777, true)) {
+                throw new RuntimeException(sprintf('Cannot write to directory "%s"', storage_path()));
+            }
+        }
+
+        return $tempDir;
+    }
+
+    /**
+     * 注册 PSR4 验证规则.
+     *
+     * @param string $directory
+     * @param array  $psr4
+     */
     protected function registerPsr4($directory, array $psr4)
     {
         $classLoader = Admin::classLoader();
@@ -395,8 +462,13 @@ class Manager
         }
     }
 
+    /**
+     * 上报异常.
+     *
+     * @param \Throwable $e
+     */
     protected function reportException(\Throwable $e)
     {
-        logger()->error($e);
+        report($e);
     }
 }

+ 84 - 228
src/Extend/ServiceProvider.php

@@ -6,7 +6,6 @@ use Dcat\Admin\Admin;
 use Dcat\Admin\Exception\RuntimeException;
 use Dcat\Admin\Support\ComposerProperty;
 use Illuminate\Support\Arr;
-use Illuminate\Support\Facades\Validator;
 use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
 use Symfony\Component\Console\Output\NullOutput;
 
@@ -24,6 +23,11 @@ abstract class ServiceProvider extends LaravelServiceProvider
      */
     protected $name;
 
+    /**
+     * @var string
+     */
+    protected $packageName;
+
     /**
      * @var string
      */
@@ -44,34 +48,6 @@ abstract class ServiceProvider extends LaravelServiceProvider
      */
     protected $css = [];
 
-    /**
-     * @var array
-     */
-    protected $menu = [];
-
-    /**
-     * @var array
-     */
-    protected $permission = [];
-
-    /**
-     * @var array
-     */
-    protected $menuValidationRules = [
-        'title' => 'required',
-        'path'  => 'required',
-        'icon'  => 'required',
-    ];
-
-    /**
-     * @var array
-     */
-    protected $permissionValidationRules = [
-        'name'  => 'required',
-        'slug'  => 'required',
-        'path'  => 'required',
-    ];
-
     /**
      * @var \Symfony\Component\Console\Output\OutputInterface
      */
@@ -92,7 +68,23 @@ abstract class ServiceProvider extends LaravelServiceProvider
     /**
      * {@inheritdoc}
      */
-    public function boot()
+    final public function boot()
+    {
+        $this->autoRegister();
+
+        if ($this->disabled()) {
+            return;
+        }
+
+        $this->init();
+    }
+
+    /**
+     * 初始化操作.
+     *
+     * @return void
+     */
+    public function init()
     {
         if ($views = $this->getViewPath()) {
             $this->loadViewsFrom($views, $this->getName());
@@ -109,14 +101,44 @@ abstract class ServiceProvider extends LaravelServiceProvider
         $this->aliasAssets();
     }
 
+    /**
+     * 自动注册扩展.
+     */
+    protected function autoRegister()
+    {
+        if (! $this->getName()) {
+            return;
+        }
+
+        Admin::extension()->addExtension($this);
+    }
+
     /**
      * 获取扩展名称.
      *
-     * @return string
+     * @return string|void
      */
     final public function getName()
     {
-        return $this->name ?: ($this->name = str_replace('/', '.', $this->composerProperty->name));
+        return $this->name ?: ($this->name = str_replace('/', '.', $this->getPackageName()));
+    }
+
+    /**
+     * 获取包名.
+     *
+     * @return string|void
+     */
+    final public function getPackageName()
+    {
+        if (! $this->packageName) {
+            if (! $this->composerProperty) {
+                return;
+            }
+
+            $this->packageName = $this->composerProperty->name;
+        }
+
+        return $this->packageName;
     }
 
     /**
@@ -250,45 +272,6 @@ abstract class ServiceProvider extends LaravelServiceProvider
         }
     }
 
-    /**
-     * 配置key.
-     *
-     * @return mixed
-     */
-    protected function getConfigKey()
-    {
-        return str_replace('.', ':', $this->getName());
-    }
-
-    /**
-     * @param $config
-     *
-     * @return false|string
-     */
-    protected function serializeConfig($config)
-    {
-        return json_encode($config);
-    }
-
-    /**
-     * @param $config
-     *
-     * @return array
-     */
-    protected function unserializeConfig($config)
-    {
-        return json_decode($config, true);
-    }
-
-    /**
-     * 导入扩展.
-     */
-    public function import()
-    {
-        $this->importMenus();
-        $this->importPermissions();
-    }
-
     /**
      * 卸载扩展.
      */
@@ -328,14 +311,10 @@ abstract class ServiceProvider extends LaravelServiceProvider
     public function registerRoutes($callback)
     {
         Admin::app()->routes(function ($router) use ($callback) {
-            $attributes = array_merge(
-                [
-                    'prefix'     => config('admin.route.prefix'),
-                    'middleware' => config('admin.route.middleware'),
-                ]
-            );
-
-            $router->group($attributes, $callback);
+            $router->group([
+                'prefix'     => config('admin.route.prefix'),
+                'middleware' => config('admin.route.middleware'),
+            ], $callback);
         });
     }
 
@@ -383,24 +362,6 @@ abstract class ServiceProvider extends LaravelServiceProvider
         return is_file($path) ? $path : null;
     }
 
-    /**
-     * 获取菜单.
-     *
-     * @return array
-     */
-    protected function menu()
-    {
-        return $this->menu;
-    }
-
-    /**
-     * @return array
-     */
-    protected function permission()
-    {
-        return $this->permission;
-    }
-
     /**
      * @param ComposerProperty $composerProperty
      *
@@ -413,170 +374,65 @@ abstract class ServiceProvider extends LaravelServiceProvider
         return $this;
     }
 
-    /**
-     * 导入菜单.
-     *
-     * @throws \Exception
-     */
-    protected function importMenus()
-    {
-        if (! ($menu = $this->menu()) || ! $this->validateMenu($menu)) {
-            return;
-        }
-
-        extract($menu);
-
-        if ($this->checkMenu($path)) {
-            $this->output->writeln("<warn>Menu [$path] already exists!</warn>");
-        } else {
-            $this->createMenu($title, $path, $icon);
-            $this->output->writeln('<info>Import extension menu succeeded!</info>');
-        }
-    }
-
-    /**
-     * 导入权限.
-     *
-     * @throws \Exception
-     */
-    protected function importPermissions()
-    {
-        if (! $this->config('admin.permission.enable')) {
-            return;
-        }
-
-        if (! ($permission = $this->permission()) || ! $this->validatePermission($permission)) {
-            return;
-        }
-
-        extract($permission);
-
-        if ($this->checkPermission($slug)) {
-            $this->output->writeln("<warn>Permission [$slug] already exists!</warn>");
-        } else {
-            $this->createPermission($name, $slug, $path);
-            $this->output->writeln('<info>Import extension permission succeeded!</info>');
-        }
-    }
-
     /**
      * 注册别名.
      */
     protected function aliasAssets()
     {
-        if ($this->js || $this->css) {
-            Admin::asset()->alias($this->getName(), $this->js, $this->css);
-        }
-    }
+        $asset = Admin::asset();
 
-    /**
-     * 验证菜单.
-     *
-     * @param array $menu
-     *
-     * @throws \Exception
-     *
-     * @return bool
-     */
-    protected function validateMenu(array $menu)
-    {
-        /** @var \Illuminate\Validation\Validator $validator */
-        $validator = Validator::make($menu, $this->menuValidationRules);
+        // 注册静态资源路径别名
+        $asset->alias($this->getName().'.path', '@extension/'.$this->getPackageName());
 
-        if ($validator->passes()) {
-            return true;
+        if ($this->js || $this->css) {
+            $asset->alias($this->getName(), [
+                'js'  => $this->formatAssetFiles($this->js),
+                'css' => $this->formatAssetFiles($this->css),
+            ]);
         }
-
-        $message = "Invalid menu:\r\n".implode("\r\n", Arr::flatten($validator->errors()->messages()));
-
-        $this->output->writeln("<error>{$message}</error>");
     }
 
     /**
-     * @param $path
+     * @param string|array $files
      *
-     * @return bool
-     */
-    protected function checkMenu($path)
-    {
-        $menuModel = config('admin.database.menu_model');
-
-        return $menuModel::where('uri', $path)->exists();
-    }
-
-    /**
-     * 验证权限.
-     *
-     * @param array $permission
-     *
-     * @throws \Exception
-     *
-     * @return bool
+     * @return mixed
      */
-    protected function validatePermission(array $permission)
+    protected function formatAssetFiles($files)
     {
-        /** @var \Illuminate\Validation\Validator $validator */
-        $validator = Validator::make($permission, $this->permissionValidationRules);
-
-        if ($validator->passes()) {
-            return true;
+        if (is_array($files)) {
+            return array_map([$this, 'formatAssetFiles'], $files);
         }
 
-        $message = "Invalid permission:\r\n".implode("\r\n", Arr::flatten($validator->errors()->messages()));
-
-        $this->output->writeln("<error>{$message}</error>");
+        return '@'.$this->getName().'.path/'.trim($files, '/');
     }
 
     /**
-     * 创建菜单.
+     * 配置key.
      *
-     * @param string $title
-     * @param string $uri
-     * @param string $icon
-     * @param int    $parentId
+     * @return mixed
      */
-    protected function createMenu($title, $uri, $icon = 'fa-bars', $parentId = 0)
+    protected function getConfigKey()
     {
-        $menuModel = config('admin.database.menu_model');
-
-        $lastOrder = $menuModel::max('order');
-
-        $menuModel::create([
-            'parent_id' => $parentId,
-            'order'     => $lastOrder + 1,
-            'title'     => $title,
-            'icon'      => $icon,
-            'uri'       => $uri,
-        ]);
+        return str_replace('.', ':', $this->getName());
     }
 
     /**
-     * @param $slug
+     * @param $config
      *
-     * @return bool
+     * @return false|string
      */
-    protected function checkPermission($slug)
+    protected function serializeConfig($config)
     {
-        $permissionModel = config('admin.database.permissions_model');
-
-        return $permissionModel::where('slug', $slug)->exists();
+        return json_encode($config);
     }
 
     /**
-     * 创建权限.
+     * @param $config
      *
-     * @param $name
-     * @param $slug
-     * @param $path
+     * @return array
      */
-    protected function createPermission($name, $slug, $path)
+    protected function unserializeConfig($config)
     {
-        $permissionModel = config('admin.database.permissions_model');
-
-        $permissionModel::create([
-            'name'      => $name,
-            'slug'      => $slug,
-            'http_path' => trim($path, '/'),
-        ]);
+        return json_decode($config, true);
     }
 }

+ 1 - 1
src/Http/Actions/Extensions/Disable.php

@@ -9,7 +9,7 @@ class Disable extends RowAction
 {
     public function title()
     {
-        return trans('admin.disable');
+        return sprintf('<span class="text-80">%s</span>', trans('admin.disable'));
     }
 
     public function handle()

+ 1 - 1
src/Http/Actions/Extensions/Enable.php

@@ -9,7 +9,7 @@ class Enable extends RowAction
 {
     public function title()
     {
-        return trans('admin.enable');
+        return sprintf('<b>%s</b>', trans('admin.enable'));
     }
 
     public function handle()

+ 1 - 1
src/Http/Actions/Extensions/Update.php

@@ -9,7 +9,7 @@ class Update extends RowAction
 {
     public function title()
     {
-        return trans('admin.upgrade_to_version', ['version' => $this->row->new_version]);
+        return sprintf('<b>%s</b>', trans('admin.upgrade_to_version', ['version' => $this->row->new_version]));
     }
 
     public function handle()

+ 23 - 16
src/Http/Controllers/ExtensionController.php

@@ -78,8 +78,7 @@ class ExtensionController extends Controller
                     ->required();
                 $create->text('namespace')
                     ->attribute('style', 'width:240px')
-                    ->placeholder('Input Namespace. Eg: DcatAdmin\\Demo')
-                    ->required();
+                    ->placeholder('Input Namespace. Eg: DcatAdmin\\Demo');
                 $create->select('type')
                     ->options([1 => trans('admin.application'), 2 => trans('admin.theme')])
                     ->attribute('style', 'width:140px!important')
@@ -93,46 +92,54 @@ class ExtensionController extends Controller
     {
         $form = new Form(new Extension());
 
-        $form->text('package_name')->rules(function () {
+        $form->hidden('name')->rules(function () {
             return [
                 'required',
                 function ($attribute, $value, $fail) {
                     if (! Helper::validateExtensionName($value)) {
                         return $fail(
-                            "[$value] is not a valid package name, please input a name like \"vendor/name\""
+                            "[$value] is not a valid package name, please type a name like \"vendor/name\""
                         );
                     }
                 },
             ];
         });
-        $form->text('namespace')->required();
-        $form->hidden('enable');
+        $form->hidden('namespace');
+        $form->hidden('type');
+
+        $self = $this;
 
-        $form->saving(function (Form $form) {
-            $package = $form->package_name;
+        $form->saving(function (Form $form) use ($self) {
+            $package = $form->name;
             $namespace = $form->namespace;
+            $type = $form->type;
 
-            if ($package && $namespace) {
-                $results = $this->createExtension($package, $namespace);
+            if ($package) {
+                $results = $self->createExtension($package, $namespace, $type);
 
-                return $form->success($results);
+                return $form
+                    ->response()
+                    ->refresh()
+                    ->timeout(10)
+                    ->success($results);
             }
         });
 
         return $form;
     }
 
-    public function createExtension($package, $namespace)
+    public function createExtension($package, $namespace, $type)
     {
         $namespace = trim($namespace, '\\');
 
         $output = new StringOutput();
 
-        Artisan::call('admin:extend', [
-            'extension'   => $package,
-            '--namespace' => $namespace,
+        Artisan::call('admin:ext-make', [
+            'name'        => $package,
+            '--namespace' => $namespace ?: 'defualt',
+            '--theme'     => $type == 2,
         ], $output);
 
-        return $output->getContent();
+        return sprintf('<pre class="bg-transparent text-white">%s</pre>', (string) $output->getContent());
     }
 }

+ 36 - 26
src/Http/Forms/InstallFromLocal.php

@@ -20,31 +20,50 @@ class InstallFromLocal extends Form implements LazyRenderable
             return $this->response()->error('Invalid arguments.');
         }
 
-        $path = $this->getFilePath($file);
+        try {
+            $path = $this->getFilePath($file);
 
-        $manager = Admin::extension();
+            $manager = Admin::extension();
 
-        $allNames = $manager->all()->keys()->toArray();
+            $allNames = $manager->all()->keys()->toArray();
 
-        $manager->extract($path);
+            $manager->extract($path);
 
-        $manager->load();
+            $manager->load();
 
-        $newAllNames = $manager->all()->keys()->toArray();
+            $newAllNames = $manager->all()->keys()->toArray();
 
-        $diff = array_diff($newAllNames, $allNames);
+            $diff = array_diff($newAllNames, $allNames);
 
-        if (! $diff) {
-            return $this->response()->error(trans('admin.invalid_extension_package'));
-        }
+            if (! $diff) {
+                return $this->response()->error(trans('admin.invalid_extension_package'));
+            }
+
+            $manager
+                ->updateManager()
+                ->update(current($diff));
 
-        $manager
-            ->updateManager()
-            ->update(current($diff));
+            return $this->response()
+                ->success(implode('<br>', $manager->updateManager()->notes))
+                ->refresh();
+        } catch (\Throwable $e) {
+            report($e);
 
-        return $this->response()
-            ->success(implode('<br>', $manager->updateManager()->notes))
-            ->refresh();
+            return $this->response()->error($e->getMessage());
+        } finally {
+            if (! empty($path)) {
+                @unlink($path);
+            }
+        }
+    }
+
+    public function form()
+    {
+        $this->file('extension')
+            ->required()
+            ->disk($this->disk())
+            ->accept('zip,arc,rar,tar.gz', 'application/zip')
+            ->autoUpload();
     }
 
     protected function getFilePath($file)
@@ -52,7 +71,7 @@ class InstallFromLocal extends Form implements LazyRenderable
         $root = config("filesystems.disks.{$this->disk()}.root");
 
         if (! $root) {
-            throw new RuntimeException(sprintf('Invalid configurations of disk [%s], missing "root".', $this->disk()));
+            throw new RuntimeException(sprintf('Missing \'root\' for disk [%s].', $this->disk()));
         }
 
         return rtrim($root, '/').'/'.$file;
@@ -62,13 +81,4 @@ class InstallFromLocal extends Form implements LazyRenderable
     {
         return config('admin.extension.disk') ?: 'local';
     }
-
-    public function form()
-    {
-        $this->file('extension')
-            ->required()
-            ->disk($this->disk())
-            ->accept('zip,arc,rar,tar.gz', 'application/zip')
-            ->autoUpload();
-    }
 }

+ 12 - 0
src/Http/JsonResponse.php

@@ -122,6 +122,18 @@ class JsonResponse
         return $this->show('error', $message);
     }
 
+    /**
+     * 设置 toastr 显示时长.
+     *
+     * @param $seconds
+     *
+     * @return $this
+     */
+    public function timeout($seconds)
+    {
+        return $this->data(['timeout' => $seconds]);
+    }
+
     /**
      * 显示确认弹窗.
      *