Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 102 additions & 1 deletion app/Commands/Tinker.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
namespace App\Commands;

use App\Client\Requests\CodeExecutionRequestData;
use App\Dto\CodeExecution;
use App\Exceptions\CommandExitException;
use Carbon\CarbonInterval;
use Exception;
use Illuminate\Foundation\Concerns\ResolvesDumpSource;
use Illuminate\Support\Sleep;
use RuntimeException;
use Saloon\Exceptions\Request\RequestException;

use function Laravel\Prompts\error;
Expand All @@ -23,8 +25,11 @@ class Tinker extends BaseCommand
{
use ResolvesDumpSource;

protected ?string $jsonDataClass = CodeExecution::class;

protected $signature = 'tinker
{environment? : The environment ID or name}
{--code= : The code to execute}
{--editor= : Open the code in the editor}
{--timeout=60 : Maximum seconds to wait for output}';

Expand All @@ -34,6 +39,8 @@ class Tinker extends BaseCommand

protected $tmpFileLastModifiedAt;

protected const POLL_INTERVAL_SECONDS = 2;

protected const RECENT_SAVE_WINDOW_SECONDS = 2;

protected ?string $editorUrl = null;
Expand All @@ -46,6 +53,10 @@ public function handle()

$environment = $this->resolvers()->environment()->include('application')->from($this->argument('environment'));

if (! $this->isInteractive() || $this->option('code')) {
return $this->handleNonInteractively($environment);
}

$this->resolveEditorUrl();

if ($this->editorUrl) {
Expand Down Expand Up @@ -109,7 +120,7 @@ public function handle()
return $codeExecution;
}

Sleep::for(CarbonInterval::second(2));
Sleep::for(CarbonInterval::seconds(static::POLL_INTERVAL_SECONDS));
}
}, 'Waiting for output...');

Expand All @@ -133,6 +144,96 @@ public function handle()
}
}

protected function handleNonInteractively($environment): int
{
$code = $this->option('code');

if (! $code) {
throw new RuntimeException('The --code option is required in non-interactive mode.');
}

try {
$codeExecution = $this->client->codeExecutions()->create(
new CodeExecutionRequestData(
environmentId: $environment->id,
code: $code,
),
);
} catch (RequestException $e) {
if ($e->getResponse()->status() === 422) {
$errors = $e->getResponse()->json('errors', []);
$message = ! empty($errors)
? collect($errors)->map(fn ($msgs, $field) => ucwords($field).': '.implode(', ', $msgs))->implode('; ')
: $e->getResponse()->json('message', 'Validation error');

throw new RuntimeException($message);
}

throw new RuntimeException($e->getMessage());
}

if ($this->wantsJson()) {
$codeExecution = $this->pollNonInteractively($codeExecution);
} else {
$codeExecution = spin(
fn () => $this->pollNonInteractively($codeExecution),
'Waiting for output...',
);
}

$this->outputJsonIfWanted($codeExecution);

if ($codeExecution->failureReason) {
$this->failAndExit($codeExecution->failureReason);
}

if ($codeExecution->exitCode !== 0 && $codeExecution->exitCode !== null) {
if ($codeExecution->output) {
codeBlock($codeExecution->output, 'result');
}

$this->failAndExit('Code execution failed (exit code: '.$codeExecution->exitCode.').');
}

if ($codeExecution->output) {
codeBlock($codeExecution->output, 'result');
}

return self::SUCCESS;
}

protected function pollNonInteractively(CodeExecution $codeExecution): CodeExecution
{
$startedAt = time();
$timeout = (int) $this->option('timeout');
$lastStatus = '';

while (true) {
if (time() - $startedAt >= $timeout) {
throw new RuntimeException('Code execution timed out.');
}

$codeExecution = $this->client->codeExecutions()->get($codeExecution->id);

$currentStatus = $codeExecution->status->label();

if ($currentStatus !== $lastStatus) {
$this->writeJsonIfWanted([
'code_execution_id' => $codeExecution->id,
'status' => $codeExecution->status->value,
'message' => $currentStatus,
]);
$lastStatus = $currentStatus;
}

if ($codeExecution->output !== null) {
return $codeExecution;
}

Sleep::for(CarbonInterval::seconds(static::POLL_INTERVAL_SECONDS));
}
}

protected function resolveEditorUrl()
{
if ($this->input->getParameterOption('--editor', false) === false) {
Expand Down
Loading