setName('extension:make') ->setDescription('生成扩展脚手架骨架(后端+前端,可选 Service/Entity/FFI/Frontend)') ->addArgument('module', Argument::REQUIRED, '扩展模块名(目录名,建议小写下划线)') ->addOption('with-api', null, Option::VALUE_NONE, '生成 API 路由与控制器') ->addOption('with-admin', null, Option::VALUE_NONE, '生成 Admin 路由与控制器') ->addOption('with-service', null, Option::VALUE_NONE, '生成 Service 层') ->addOption('with-entity', null, Option::VALUE_NONE, '生成 Entity 层') ->addOption('with-migration', null, Option::VALUE_NONE, '生成迁移与安装/卸载 SQL 样板') ->addOption('with-frontend', null, Option::VALUE_NONE, '生成前端 Admin 模板') ->addOption('with-ffi', null, Option::VALUE_NONE, '生成 FFI 目录与构建脚本样板') ->addOption('adapter', null, Option::VALUE_REQUIRED, '服务适配默认实现 php|ffi|auto', 'php') ->addOption('desc', null, Option::VALUE_REQUIRED, 'info.php 描述', '') ->addOption('author', null, Option::VALUE_REQUIRED, '作者', 'yourname') ->addOption('dry-run', null, Option::VALUE_NONE, '仅预览将要创建的目录与文件,不实际写入') ->addOption('force', null, Option::VALUE_NONE, '允许在已存在的模块目录内覆盖写入文件'); } protected function execute(Input $input, Output $output): int { $module = (string)$input->getArgument('module'); $withApi = (bool)$input->getOption('with-api'); $withAdmin = (bool)$input->getOption('with-admin'); $withService = (bool)$input->getOption('with-service'); $withEntity = (bool)$input->getOption('with-entity'); $withMigration = (bool)$input->getOption('with-migration'); $withFrontend = (bool)$input->getOption('with-frontend'); $withFFI = (bool)$input->getOption('with-ffi'); $adapter = (string)$input->getOption('adapter'); $desc = (string)$input->getOption('desc'); $author = (string)$input->getOption('author'); $dryRun = (bool)$input->getOption('dry-run'); $force = (bool)$input->getOption('force'); if (!$module) { $output->error('模块名不能为空'); return 1; } // 默认行为:如果用户未显式指定任何 with-* 选项,则默认生成“完整插件”(除 FFI) $anySpecified = $withApi || $withAdmin || $withService || $withEntity || $withMigration || $withFrontend || $withFFI; if (!$anySpecified) { $withApi = $withAdmin = $withService = $withEntity = $withMigration = $withFrontend = true; // $withFFI 默认为 false,避免环境未开启 FFI 导致构建失败 } $base = rtrim(extension_path($module), '/'); if (is_dir($base) && !$force) { $output->error("扩展 {$module} 已存在:{$base},可使用 --force 覆盖写入"); return 1; } // 目录结构 $dirs = [ "$base/src/Controller/Api", "$base/src/Controller/Admin", "$base/src/Service", "$base/src/Entity", "$base/src/Hook", "$base/database/migrations", "$base/database/seeds", "$base/route", "$base/config", ]; // 计划文件(用于 dry-run 展示) $ns = "SixShop\\\\Extension\\\\{$module}"; $studly = str_replace(['-', '_'], '', ucwords($module, '-_')); $planFiles = [ "$base/info.php", "$base/config.php", "$base/README.md", "$base/src/Extension.php", "$base/src/Hook/{$studly}Hook.php", ]; if ($withApi) { $planFiles[] = "$base/route/api.php"; $planFiles[] = "$base/src/Controller/Api/HelloController.php"; $planFiles[] = "$base/src/Controller/Api/ItemController.php"; } if ($withAdmin) { $planFiles[] = "$base/route/admin.php"; $planFiles[] = "$base/src/Controller/Admin/DashboardController.php"; $planFiles[] = "$base/src/Controller/Admin/ManageController.php"; $planFiles[] = "$base/src/Controller/Admin/ItemController.php"; $planFiles[] = "$base/src/Controller/Admin/UploadController.php"; } if ($withService) $planFiles[] = "$base/src/Service/{$studly}Service.php"; if ($withEntity) $planFiles[] = "$base/src/Entity/{$studly}.php"; if ($withMigration) { $planFiles[] = "$base/config/install.sql"; $planFiles[] = "$base/config/uninstall.sql"; } if ($dryRun) { $output->writeln("[DRY-RUN] 将创建以下目录:"); foreach ($dirs as $d) { $output->writeln(" - $d"); } $output->writeln("[DRY-RUN] 将创建以下关键文件(部分):"); foreach ($planFiles as $f) { $output->writeln(" - $f"); } return 0; } foreach ($dirs as $d) @mkdir($d, 0777, true); // info.php // 生成完整 info.php(参考 guimi) $info = [ 'id' => $module, 'name' => $module, // 分类:core|content|shop|other|custom,默认 custom 'category' => 'custom', 'description' => $desc ?: ($module . ' 扩展模块'), 'version' => '0.1.0', 'core_version' => '^1.0', 'author' => $author ?: 'sixshop', 'email' => '', 'website' => '', 'image' => '', 'license' => 'MIT', 'keywords' => [], 'dependencies' => [], 'conflicts' => [], 'requires' => [ 'php' => '>=8.0.0', 'extensions' => ['json', 'pdo'], ], ]; $infoExport = var_export($info, true); $infoExport = str_replace(['array (', ')'], ['[', ']'], $infoExport); file_put_contents("$base/info.php", " [ [ 'type' => 'input', 'field' => 'title', 'title' => '标题', 'value' => '', 'props' => ['placeholder' => '请输入标题'], ], ], ]; PHP; file_put_contents("$base/config.php", $configPhp); // README file_put_contents("$base/README.md", "# {$module}\n\n自动生成的扩展骨架。\n"); // 上面已生成 Extension.php,这里不再重复生成 // Hook 占位(下面统一生成一次) // 安装/卸载 SQL 样板 if ($withMigration) { $install = "-- 安装 SQL 示例\n"; $uninstall = "-- 卸载 SQL 示例\n"; file_put_contents("$base/config/install.sql", $install); file_put_contents("$base/config/uninstall.sql", $uninstall); } // 路由(注意:系统会自动加 /api/{$module} 或 /admin/{$module} 前缀,这里不需要再包一层模块分组) $apiRoute = sprintf(<<<'PHP' json(['code' => 0, 'msg' => 'ok', 'data' => ['pong' => true]]))->middleware(['auth']); // 示例:业务分组-具体动作(放在资源路由之前,避免 :id 冲突) Route::group('item', function () { Route::get('info', [ItemController::class, 'info']); Route::post('check', [ItemController::class, 'check']); })->middleware(['auth']); PHP, $module); $adminRoute = sprintf(<<<'PHP' middleware(['auth']); // 可按需继续追加:relation-trend / verification-trend / redemption-trend / latest 等 // 通用上传 Route::post('upload', [UploadController::class, 'handle'])->middleware(['auth']); PHP, $module, $module, $module); if ($withApi) file_put_contents("$base/route/api.php", $apiRoute); if ($withAdmin) file_put_contents("$base/route/admin.php", $adminRoute); // 控制器样板 if ($withApi) { $apiCtrl = sprintf(<<<'PHP' userID ?? ($r->adminID ?? null); } public function index(Request $r): Response { if (!$this->uid($r)) return json(['code'=>401,'msg'=>'未登录']); return json(['code'=>0,'msg'=>'ok','data'=>['now'=>date('c')]]); } } PHP, $ns); file_put_contents("$base/src/Controller/Api/HelloController.php", $apiCtrl); // API 资源控制器 $apiItemCtrl = sprintf(<<<'PHP' userID ?? ($r->adminID ?? null); } public function index(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>['list'=>[], 'total'=>0]]); } public function read(int $id): Response { return json(['code'=>0,'msg'=>'ok','data'=>['id'=>$id]]); } public function save(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); } public function update(int $id, Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); } public function delete(int $id): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); } // 具体动作示例(与路由匹配) public function info(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>['info'=>[]]]); } public function check(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); } } PHP, $ns); file_put_contents("$base/src/Controller/Api/ItemController.php", $apiItemCtrl); } if ($withAdmin) { $adminCtrl = sprintf(<<<'PHP' 0,'msg'=>'ok','data'=>['list'=>[], 'total'=>0]]); } } PHP, $ns); file_put_contents("$base/src/Controller/Admin/ManageController.php", $adminCtrl); // 首页/仪表盘控制器(对齐 guimi:dashboard/*) $dashboardCtrl = sprintf(<<<'PHP' 0, 'msg' => 'ok', 'data' => [ 'cards' => [ ['title' => '总数', 'value' => 0], ], ]]); } } PHP, $ns); file_put_contents("$base/src/Controller/Admin/DashboardController.php", $dashboardCtrl); // Admin 资源控制器 $adminItemCtrl = sprintf(<<<'PHP' 0,'msg'=>'ok','data'=>['list'=>[], 'total'=>0]]); } public function read(int $id): Response { return json(['code'=>0,'msg'=>'ok','data'=>['id'=>$id]]); } public function save(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); } public function update(int $id, Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); } public function delete(int $id): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); } } PHP, $ns); file_put_contents("$base/src/Controller/Admin/ItemController.php", $adminItemCtrl); // Admin 上传控制器 $uploadCtrl = sprintf(<<<'PHP' 0,'msg'=>'ok','data'=>['url'=>'','name'=>'']]); } } PHP, $ns); file_put_contents("$base/src/Controller/Admin/UploadController.php", $uploadCtrl); } // Service / Entity 占位 if ($withService) { $svc = sprintf(<<<'PHP' true]; } } PHP, $ns, $studly); file_put_contents("$base/src/Service/{$studly}Service.php", $svc); } if ($withEntity) { $ent = sprintf(<<<'PHP'