2022-08-25 17:10:06 +00:00
|
|
|
|
<?php
|
|
|
|
|
// +----------------------------------------------------------------------
|
|
|
|
|
// | BuildAdmin [ Quickly create commercial-grade management system using popular technology stack ]
|
|
|
|
|
// +----------------------------------------------------------------------
|
|
|
|
|
// | Copyright (c) 2022~2022 http://buildadmin.com All rights reserved.
|
|
|
|
|
// +----------------------------------------------------------------------
|
|
|
|
|
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
|
|
|
|
// +----------------------------------------------------------------------
|
2022-09-14 03:48:50 +00:00
|
|
|
|
// | Author: 妙码生花 <hi@buildadmin.com>
|
2022-08-25 17:10:06 +00:00
|
|
|
|
// +----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
namespace ba;
|
|
|
|
|
|
2023-06-25 04:01:03 +00:00
|
|
|
|
use Throwable;
|
2022-08-25 17:10:06 +00:00
|
|
|
|
use think\Response;
|
|
|
|
|
use think\facade\Config;
|
|
|
|
|
use app\admin\library\Auth;
|
2023-06-25 04:01:03 +00:00
|
|
|
|
use app\admin\library\module\Manage;
|
2022-08-25 17:10:06 +00:00
|
|
|
|
use think\exception\HttpResponseException;
|
2024-03-28 08:27:02 +00:00
|
|
|
|
use app\common\library\token\TokenExpirationException;
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
class Terminal
|
|
|
|
|
{
|
|
|
|
|
/**
|
2023-06-25 04:01:03 +00:00
|
|
|
|
* @var ?Terminal 对象实例
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-25 04:01:03 +00:00
|
|
|
|
protected static ?Terminal $instance = null;
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2023-06-22 21:55:51 +00:00
|
|
|
|
* @var string 当前执行的命令 $command 的 key
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
protected string $commandKey = '';
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2023-06-22 21:55:51 +00:00
|
|
|
|
* @var array proc_open 的参数
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
protected array $descriptorsPec = [];
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
2023-06-22 21:55:51 +00:00
|
|
|
|
/**
|
2023-06-25 04:01:03 +00:00
|
|
|
|
* @var resource|bool proc_open 返回的 resource
|
2023-06-22 21:55:51 +00:00
|
|
|
|
*/
|
|
|
|
|
protected $process = false;
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
2023-06-22 21:55:51 +00:00
|
|
|
|
/**
|
|
|
|
|
* @var array proc_open 的管道
|
|
|
|
|
*/
|
|
|
|
|
protected array $pipes = [];
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
2023-06-22 21:55:51 +00:00
|
|
|
|
/**
|
2024-03-11 06:26:51 +00:00
|
|
|
|
* @var int proc执行状态:0=未执行,1=执行中,2=执行完毕
|
2023-06-22 21:55:51 +00:00
|
|
|
|
*/
|
2024-03-11 06:26:51 +00:00
|
|
|
|
protected int $procStatusMark = 0;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var array proc执行状态数据
|
|
|
|
|
*/
|
|
|
|
|
protected array $procStatusData = [];
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2023-06-22 21:55:51 +00:00
|
|
|
|
* @var string 命令在前台的uuid
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
protected string $uuid = '';
|
2022-08-26 16:23:40 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2023-06-22 21:55:51 +00:00
|
|
|
|
* @var string 扩展信息
|
2022-08-26 16:23:40 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
protected string $extend = '';
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2023-06-22 21:55:51 +00:00
|
|
|
|
* @var string 命令执行输出文件
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
protected string $outputFile = '';
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2023-06-22 21:55:51 +00:00
|
|
|
|
* @var string 命令执行实时输出内容
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
protected string $outputContent = '';
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2023-06-22 21:55:51 +00:00
|
|
|
|
* @var string 自动构建的前端文件的 outDir(相对于根目录)
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
protected static string $distDir = 'web' . DIRECTORY_SEPARATOR . 'dist';
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2023-06-22 21:55:51 +00:00
|
|
|
|
* @var array 状态标识
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
protected array $flag = [
|
2022-08-25 17:10:06 +00:00
|
|
|
|
// 连接成功
|
|
|
|
|
'link-success' => 'command-link-success',
|
|
|
|
|
// 执行成功
|
|
|
|
|
'exec-success' => 'command-exec-success',
|
|
|
|
|
// 执行完成
|
|
|
|
|
'exec-completed' => 'command-exec-completed',
|
2022-08-25 17:40:22 +00:00
|
|
|
|
// 执行出错
|
2022-08-25 17:10:06 +00:00
|
|
|
|
'exec-error' => 'command-exec-error',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 初始化
|
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public static function instance(): Terminal
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
if (is_null(self::$instance)) {
|
|
|
|
|
self::$instance = new static();
|
|
|
|
|
}
|
|
|
|
|
return self::$instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造函数
|
|
|
|
|
*/
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
2023-06-22 21:55:51 +00:00
|
|
|
|
$this->uuid = request()->param('uuid', '');
|
|
|
|
|
$this->extend = request()->param('extend', '');
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
// 初始化日志文件
|
|
|
|
|
$outputDir = root_path() . 'runtime' . DIRECTORY_SEPARATOR . 'terminal';
|
|
|
|
|
$this->outputFile = $outputDir . DIRECTORY_SEPARATOR . 'exec.log';
|
|
|
|
|
if (!is_dir($outputDir)) {
|
|
|
|
|
mkdir($outputDir, 0755, true);
|
|
|
|
|
}
|
|
|
|
|
file_put_contents($this->outputFile, '');
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 命令执行结果输出到文件而不是管道
|
|
|
|
|
* 因为输出到管道时有延迟,而文件虽然需要频繁读取和对比内容,但是输出实时的
|
|
|
|
|
*/
|
|
|
|
|
$this->descriptorsPec = [0 => ['pipe', 'r'], 1 => ['file', $this->outputFile, 'w'], 2 => ['file', $this->outputFile, 'w']];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取命令
|
|
|
|
|
* @param string $key 命令key
|
2023-06-25 04:01:03 +00:00
|
|
|
|
* @return array|bool
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public static function getCommand(string $key): bool|array
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
if (!$key) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$commands = Config::get('terminal.commands');
|
|
|
|
|
if (stripos($key, '.')) {
|
|
|
|
|
$key = explode('.', $key);
|
|
|
|
|
if (!array_key_exists($key[0], $commands) || !is_array($commands[$key[0]]) || !array_key_exists($key[1], $commands[$key[0]])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$command = $commands[$key[0]][$key[1]];
|
|
|
|
|
} else {
|
|
|
|
|
if (!array_key_exists($key, $commands)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$command = $commands[$key];
|
|
|
|
|
}
|
|
|
|
|
if (!is_array($command)) {
|
|
|
|
|
$command = [
|
|
|
|
|
'cwd' => root_path(),
|
|
|
|
|
'command' => $command,
|
|
|
|
|
];
|
|
|
|
|
} else {
|
|
|
|
|
$command = [
|
|
|
|
|
'cwd' => root_path() . $command['cwd'],
|
|
|
|
|
'command' => $command['command'],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
$command['cwd'] = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $command['cwd']);
|
|
|
|
|
return $command;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 21:55:51 +00:00
|
|
|
|
/**
|
|
|
|
|
* 执行命令
|
|
|
|
|
* @param bool $authentication 是否鉴权
|
2023-06-25 04:01:03 +00:00
|
|
|
|
* @throws Throwable
|
2023-06-22 21:55:51 +00:00
|
|
|
|
*/
|
|
|
|
|
public function exec(bool $authentication = true): void
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
2024-03-15 04:04:47 +00:00
|
|
|
|
$this->sendHeader();
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
2023-10-13 17:44:55 +00:00
|
|
|
|
while (ob_get_level()) {
|
|
|
|
|
ob_end_clean();
|
|
|
|
|
}
|
2022-08-25 17:10:06 +00:00
|
|
|
|
if (!ob_get_level()) ob_start();
|
|
|
|
|
|
2023-10-13 17:44:55 +00:00
|
|
|
|
$this->commandKey = request()->param('command');
|
|
|
|
|
|
2022-08-25 17:10:06 +00:00
|
|
|
|
$command = self::getCommand($this->commandKey);
|
|
|
|
|
if (!$command) {
|
|
|
|
|
$this->execError('The command was not allowed to be executed', true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($authentication) {
|
2024-03-28 08:27:02 +00:00
|
|
|
|
try {
|
|
|
|
|
$token = get_auth_token();
|
|
|
|
|
$auth = Auth::instance();
|
|
|
|
|
$auth->init($token);
|
|
|
|
|
|
|
|
|
|
if (!$auth->isLogin() || !$auth->isSuperAdmin()) {
|
|
|
|
|
$this->execError("You are not super administrator or not logged in", true);
|
|
|
|
|
}
|
|
|
|
|
} catch (TokenExpirationException) {
|
|
|
|
|
$this->execError(__('Token expiration'));
|
2022-08-25 17:10:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->beforeExecution();
|
|
|
|
|
$this->outputFlag('link-success');
|
|
|
|
|
$this->output('> ' . $command['command'], false);
|
|
|
|
|
|
|
|
|
|
$this->process = proc_open($command['command'], $this->descriptorsPec, $this->pipes, $command['cwd']);
|
|
|
|
|
if (!is_resource($this->process)) {
|
|
|
|
|
$this->execError('Failed to execute', true);
|
|
|
|
|
}
|
|
|
|
|
while ($this->getProcStatus()) {
|
|
|
|
|
$contents = file_get_contents($this->outputFile);
|
|
|
|
|
if (strlen($contents) && $this->outputContent != $contents) {
|
|
|
|
|
$newOutput = str_replace($this->outputContent, '', $contents);
|
|
|
|
|
if (preg_match('/\r\n|\r|\n/', $newOutput)) {
|
|
|
|
|
$this->output($newOutput);
|
|
|
|
|
$this->outputContent = $contents;
|
2024-03-11 07:08:16 +00:00
|
|
|
|
$this->checkOutput();
|
2022-08-25 17:10:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-11 06:26:51 +00:00
|
|
|
|
|
|
|
|
|
// 输出执行状态信息
|
|
|
|
|
if ($this->procStatusMark === 2) {
|
|
|
|
|
$this->output('exitCode: ' . $this->procStatusData['exitcode']);
|
|
|
|
|
if ($this->procStatusData['exitcode'] === 0) {
|
|
|
|
|
if ($this->successCallback()) {
|
|
|
|
|
$this->outputFlag('exec-success');
|
|
|
|
|
} else {
|
|
|
|
|
$this->output('Error: Command execution succeeded, but callback execution failed');
|
|
|
|
|
$this->outputFlag('exec-error');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$this->outputFlag('exec-error');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-14 06:13:59 +00:00
|
|
|
|
usleep(500000);
|
2022-08-25 17:10:06 +00:00
|
|
|
|
}
|
|
|
|
|
foreach ($this->pipes as $pipe) {
|
|
|
|
|
fclose($pipe);
|
|
|
|
|
}
|
|
|
|
|
proc_close($this->process);
|
|
|
|
|
$this->outputFlag('exec-completed');
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-25 04:01:03 +00:00
|
|
|
|
/**
|
|
|
|
|
* 获取执行状态
|
|
|
|
|
* @throws Throwable
|
|
|
|
|
*/
|
2022-08-25 18:29:45 +00:00
|
|
|
|
public function getProcStatus(): bool
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
2024-03-11 06:26:51 +00:00
|
|
|
|
$this->procStatusData = proc_get_status($this->process);
|
|
|
|
|
if ($this->procStatusData['running']) {
|
|
|
|
|
$this->procStatusMark = 1;
|
2022-08-25 17:10:06 +00:00
|
|
|
|
return true;
|
2024-03-11 06:26:51 +00:00
|
|
|
|
} elseif ($this->procStatusMark === 1) {
|
|
|
|
|
$this->procStatusMark = 2;
|
2022-08-25 17:10:06 +00:00
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 输出 EventSource 数据
|
|
|
|
|
* @param string $data
|
|
|
|
|
* @param bool $callback
|
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public function output(string $data, bool $callback = true): void
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
$data = self::outputFilter($data);
|
|
|
|
|
$data = [
|
|
|
|
|
'data' => $data,
|
2022-08-26 16:23:40 +00:00
|
|
|
|
'uuid' => $this->uuid,
|
|
|
|
|
'extend' => $this->extend,
|
2022-08-25 17:10:06 +00:00
|
|
|
|
'key' => $this->commandKey,
|
|
|
|
|
];
|
|
|
|
|
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
|
|
|
|
|
if ($data) {
|
2024-03-15 04:04:47 +00:00
|
|
|
|
$this->finalOutput($data);
|
2022-08-25 17:10:06 +00:00
|
|
|
|
if ($callback) $this->outputCallback($data);
|
|
|
|
|
@ob_flush();// 刷新浏览器缓冲区
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-11 07:08:16 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查输出
|
|
|
|
|
*/
|
|
|
|
|
public function checkOutput(): void
|
|
|
|
|
{
|
|
|
|
|
if (str_contains($this->outputContent, '(Y/n)')) {
|
|
|
|
|
$this->execError('An interactive command has been detected, and you can manually execute the command to confirm the situation.', true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-25 17:10:06 +00:00
|
|
|
|
/**
|
|
|
|
|
* 输出状态标记
|
|
|
|
|
* @param string $flag
|
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public function outputFlag(string $flag): void
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
$this->output($this->flag[$flag], false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 输出后回调
|
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public function outputCallback($data): void
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-25 18:29:45 +00:00
|
|
|
|
/**
|
|
|
|
|
* 成功后回调
|
|
|
|
|
* @return bool
|
2023-06-25 04:01:03 +00:00
|
|
|
|
* @throws Throwable
|
2022-08-25 18:29:45 +00:00
|
|
|
|
*/
|
|
|
|
|
public function successCallback(): bool
|
|
|
|
|
{
|
|
|
|
|
if (stripos($this->commandKey, '.')) {
|
|
|
|
|
$commandKeyArr = explode('.', $this->commandKey);
|
|
|
|
|
$commandPKey = $commandKeyArr[0] ?? '';
|
|
|
|
|
} else {
|
|
|
|
|
$commandPKey = $this->commandKey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($commandPKey == 'web-build') {
|
2022-08-26 04:49:30 +00:00
|
|
|
|
if (!self::mvDist()) {
|
2022-08-25 18:29:45 +00:00
|
|
|
|
$this->output('Build succeeded, but move file failed. Please operate manually.');
|
2022-08-26 04:49:30 +00:00
|
|
|
|
return false;
|
2022-08-25 18:29:45 +00:00
|
|
|
|
}
|
2022-08-26 16:43:40 +00:00
|
|
|
|
} elseif ($commandPKey == 'web-install' && $this->extend) {
|
|
|
|
|
[$type, $value] = explode(':', $this->extend);
|
|
|
|
|
if ($type == 'module-install' && $value) {
|
|
|
|
|
Manage::instance($value)->dependentInstallComplete('npm');
|
|
|
|
|
}
|
|
|
|
|
} elseif ($commandPKey == 'composer' && $this->extend) {
|
|
|
|
|
[$type, $value] = explode(':', $this->extend);
|
|
|
|
|
if ($type == 'module-install' && $value) {
|
|
|
|
|
Manage::instance($value)->dependentInstallComplete('composer');
|
|
|
|
|
}
|
2023-03-02 02:14:22 +00:00
|
|
|
|
} elseif ($commandPKey == 'nuxt-install' && $this->extend) {
|
|
|
|
|
[$type, $value] = explode(':', $this->extend);
|
|
|
|
|
if ($type == 'module-install' && $value) {
|
|
|
|
|
Manage::instance($value)->dependentInstallComplete('nuxt_npm');
|
|
|
|
|
}
|
2022-08-25 18:29:45 +00:00
|
|
|
|
}
|
2022-08-26 04:49:30 +00:00
|
|
|
|
return true;
|
2022-08-25 18:29:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-25 17:10:06 +00:00
|
|
|
|
/**
|
|
|
|
|
* 执行前埋点
|
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public function beforeExecution(): void
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
if ($this->commandKey == 'test.pnpm') {
|
|
|
|
|
@unlink(root_path() . 'public' . DIRECTORY_SEPARATOR . 'npm-install-test' . DIRECTORY_SEPARATOR . 'pnpm-lock.yaml');
|
|
|
|
|
} elseif ($this->commandKey == 'web-install.pnpm') {
|
|
|
|
|
@unlink(root_path() . 'web' . DIRECTORY_SEPARATOR . 'pnpm-lock.yaml');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 输出过滤
|
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public static function outputFilter($str): string
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
$str = trim($str);
|
|
|
|
|
$preg = '/\[(.*?)m/i';
|
|
|
|
|
$str = preg_replace($preg, '', $str);
|
2023-06-22 21:55:51 +00:00
|
|
|
|
$str = str_replace(["\r\n", "\r", "\n"], "\n", $str);
|
2022-08-25 17:10:06 +00:00
|
|
|
|
return mb_convert_encoding($str, 'UTF-8', 'UTF-8,GBK,GB2312,BIG5');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 执行错误
|
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public function execError($error, $break = false): void
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
$this->output('Error:' . $error);
|
|
|
|
|
$this->outputFlag('exec-error');
|
|
|
|
|
if ($break) $this->break();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 退出执行
|
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public function break(): void
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
throw new HttpResponseException(Response::create()->contentType('text/event-stream'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 执行一个命令并以字符串的方式返回执行输出
|
|
|
|
|
* 代替 exec 使用,这样就只需要解除 proc_open 的函数禁用了
|
|
|
|
|
* @param $commandKey
|
2023-06-25 04:01:03 +00:00
|
|
|
|
* @return string|bool
|
2022-08-25 17:10:06 +00:00
|
|
|
|
*/
|
2023-06-22 21:55:51 +00:00
|
|
|
|
public static function getOutputFromProc($commandKey): bool|string
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
if (!function_exists('proc_open') || !function_exists('proc_close')) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$command = self::getCommand($commandKey);
|
|
|
|
|
if (!$command) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$descriptorsPec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
|
|
|
|
|
$process = proc_open($command['command'], $descriptorsPec, $pipes, null, null);
|
|
|
|
|
if (is_resource($process)) {
|
|
|
|
|
$info = stream_get_contents($pipes[1]);
|
|
|
|
|
$info .= stream_get_contents($pipes[2]);
|
|
|
|
|
fclose($pipes[1]);
|
|
|
|
|
fclose($pipes[2]);
|
|
|
|
|
proc_close($process);
|
|
|
|
|
return self::outputFilter($info);
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-29 10:56:30 +00:00
|
|
|
|
public static function mvDist(): bool
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
$distPath = root_path() . self::$distDir . DIRECTORY_SEPARATOR;
|
|
|
|
|
$indexHtmlPath = $distPath . 'index.html';
|
|
|
|
|
$assetsPath = $distPath . 'assets';
|
|
|
|
|
if (!file_exists($indexHtmlPath) || !file_exists($assetsPath)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$toIndexHtmlPath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'index.html';
|
|
|
|
|
$toAssetsPath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'assets';
|
|
|
|
|
@unlink($toIndexHtmlPath);
|
2023-06-24 18:35:30 +00:00
|
|
|
|
Filesystem::delDir($toAssetsPath);
|
2022-08-25 17:10:06 +00:00
|
|
|
|
|
|
|
|
|
if (rename($indexHtmlPath, $toIndexHtmlPath) && rename($assetsPath, $toAssetsPath)) {
|
2023-06-24 18:35:30 +00:00
|
|
|
|
Filesystem::delDir($distPath);
|
2022-08-25 17:10:06 +00:00
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-29 10:56:30 +00:00
|
|
|
|
public static function changeTerminalConfig($config = []): bool
|
2022-08-25 17:10:06 +00:00
|
|
|
|
{
|
|
|
|
|
// 不保存在数据库中,因为切换包管理器时,数据库资料可能还未配置
|
|
|
|
|
$oldPackageManager = Config::get('terminal.npm_package_manager');
|
|
|
|
|
$newPackageManager = request()->post('manager', $config['manager'] ?? $oldPackageManager);
|
|
|
|
|
|
2024-09-08 10:43:26 +00:00
|
|
|
|
if ($oldPackageManager == $newPackageManager) {
|
2022-08-25 17:10:06 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$buildConfigFile = config_path() . 'terminal.php';
|
|
|
|
|
$buildConfigContent = @file_get_contents($buildConfigFile);
|
|
|
|
|
$buildConfigContent = preg_replace("/'npm_package_manager'(\s+)=>(\s+)'$oldPackageManager'/", "'npm_package_manager'\$1=>\$2'$newPackageManager'", $buildConfigContent);
|
|
|
|
|
$result = @file_put_contents($buildConfigFile, $buildConfigContent);
|
|
|
|
|
return (bool)$result;
|
|
|
|
|
}
|
2024-03-15 04:04:47 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 最终输出
|
|
|
|
|
*/
|
|
|
|
|
public function finalOutput(string $data): void
|
|
|
|
|
{
|
|
|
|
|
$app = app();
|
|
|
|
|
if (!empty($app->worker) && !empty($app->connection)) {
|
|
|
|
|
$app->connection->send(new \Workerman\Protocols\Http\ServerSentEvents(['event' => 'message', 'data' => $data]));
|
|
|
|
|
} else {
|
|
|
|
|
echo 'data: ' . $data . "\n\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 发送响应头
|
|
|
|
|
*/
|
|
|
|
|
public function sendHeader(): void
|
|
|
|
|
{
|
|
|
|
|
$headers = array_merge(request()->allowCrossDomainHeaders ?? [], [
|
|
|
|
|
'X-Accel-Buffering' => 'no',
|
|
|
|
|
'Content-Type' => 'text/event-stream',
|
|
|
|
|
'Cache-Control' => 'no-cache',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$app = app();
|
|
|
|
|
if (!empty($app->worker) && !empty($app->connection)) {
|
|
|
|
|
$app->connection->send(new \Workerman\Protocols\Http\Response(200, $headers, "\r\n"));
|
|
|
|
|
} else {
|
|
|
|
|
foreach ($headers as $name => $val) {
|
|
|
|
|
header($name . (!is_null($val) ? ':' . $val : ''));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-25 17:10:06 +00:00
|
|
|
|
}
|