glugox/action-runner

Headless action runner for Laravel that executes Artisan commands or Nova-like actions, stores logs and progress, and broadcasts output in real time.

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/glugox/action-runner

v1.0.0 2025-10-20 23:23 UTC

README

Headless Laravel package to execute Artisan commands or Laravel Nova-like model actions, persist logs + progress to DB, and broadcast real-time output so any frontend (Vue/React/Inertia) can render consoles and progress bars.

Install

composer require glugox/action-runner
php artisan vendor:publish --provider="Glugox\\ActionRunner\\ActionRunnerServiceProvider" --tag=migrations
php artisan migrate

No UI shipped. Bring your own frontend and subscribe to broadcasts.

Publish the config if you need to customise the route prefix, middleware, or registered actions:

php artisan vendor:publish --provider="Glugox\\ActionRunner\\ActionRunnerServiceProvider" --tag=config

Registered actions act as an allow-list. Each entry's key is used in the HTTP API and maps to the action class plus the models it is allowed to target:

// config/action-runner.php

'actions' => [
    'publish-posts' => [
        'class' => App\\Actions\\PublishPosts::class,
        'models' => [App\\Models\\Post::class],
    ],
],

⚠️ Keep these routes behind authentication/authorization middleware (e.g. ['api', 'auth:sanctum']) to ensure only trusted clients can trigger them.

API

Run Artisan Command

POST /action-runner/run
{
  "command": "cache:clear",
  "params": []
}

Run Nova-like Action

POST /action-runner/actions/run
{
  "action": "publish-posts",
  "model": "App\\Models\\Post",
  "ids": [1, 2, 3],
  "fields": {
    "published_at": "2024-12-01"
  }
}

Provide the model key when an action is registered for multiple models. If the action only supports a single model, the payload can omit the model property and it will default to the allowed class.

  • GET /action-runner/{id}/status

Broadcasting

Subscribe to channel action-runner.{id} and listen for events:

  • .ActionOutputUpdated{ logId, line }
  • .ActionProgressUpdated{ logId, progress }
  • .ActionStatusChanged{ logId, status }

Reporting From Your Console Command or Action

use App\Models\Post;
use Glugox\ActionRunner\ActionReporter;
use Glugox\ActionRunner\Actions\Action;
use Glugox\ActionRunner\Actions\ActionFields;
use Glugox\ActionRunner\Actions\ActionResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;

class ImportData extends \Illuminate\Console\Command
{
    protected $signature = 'import:data';

    public function handle()
    {
        ActionReporter::progress(10);
        ActionReporter::appendOutput('Starting import...');

        // Work ...

        ActionReporter::progress(90);
        ActionReporter::appendOutput('Almost done');

        ActionReporter::finish('success');
    }
}

class PublishPosts extends Action
{
    public function authorize(Request $request): bool
    {
        return $request->user()?->can('publish', Post::class) ?? false;
    }

    public function handle(ActionFields $fields, Collection $models): ActionResponse
    {
        $this->reportProgress(10);

        $models->each->update(['published_at' => $fields->get('published_at')]);

        $this->appendOutput('Posts published');
        $this->markFinished();

        return ActionResponse::success('Published ' . $models->count() . ' posts.');
    }
}

Testing

composer install
./vendor/bin/pest

This package uses Orchestra Testbench + Pest.

Queue Support

You can run actions asynchronously by passing "queued": true in your request.

POST /action-runner/run
{
  "command": "import:data",
  "params": [],
  "queued": true
}
POST /action-runner/actions/run
{
  "action": "publish-posts",
  "model": "App\\Models\\Post",
  "ids": [1, 2, 3],
  "queued": true
}
  • The response returns immediately with status: pending.
  • A RunActionJob or RunClassActionJob is dispatched to Laravel's queue system.
  • Progress/output/status are updated via ActionReporter.
  • Make sure your queue worker is running: php artisan queue:work.