Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Artisan Menu Laravel Package

jord-jd/artisan-menu

jord-jd/artisan-menu is a Laravel package for building interactive Artisan console menus. Define options and actions with a simple API to create selectable CLI prompts for commands, setup scripts, and developer tools, turning complex terminal workflows into navigable menus.

View on GitHub
Deep Wiki
Context7

Getting Started

What it buys you: interactive prompting for Artisan command arguments and options, so a command that normally requires a long list of CLI flags can be driven conversationally — including optional arguments, command-specific flags, repeatable (array) options, and safe cancellation when the user hits Escape mid-input. v4.0.0 is the first release that covers the whole matrix cleanly (Laravel 5.1 → 13, PHP 7.1+), so it is a reasonable pin for packages/apps that must support both ancient and current Laravel.

First steps

  1. composer require <vendor>/<package>:^4.0 and confirm the resolved version of symfony/console in composer.lock — prompt rendering and key handling come from Symfony Console and differ between Symfony 2.7 (Laravel 5.1/5.2) and 7.x (Laravel 11–13).
  2. Read the README section on the trait/base class first, then the config file (if published) — that is where prompt defaults, and the "recent commands" store location/TTL if present, are defined.
  3. Look at the examples for prompt for optional argument and repeatable option first; those are the two new behaviours in v4 and the ones most likely to surprise you in CI.
  4. First use case worth wiring: an internal, destructive command — e.g. a data-pruner or importer — where you want a confirmation + a couple of parameters instead of five required flags. It is the smallest change that proves the prompt flow and the Escape/cancel path end-to-end.
  5. Immediately add the command to a test with $this->artisan(...)->expectsQuestion(...) and one test that runs it with --no-interaction asserting defaults. Do this before writing more prompted commands.

Implementation Patterns

Trait on an existing command (least invasive). Keep your $signature exactly as-is, add the package trait, and declare which arguments/options should be prompted for when they are absent. Everything that already works via flags keeps working; prompting is just a fallback path.

protected $signature = 'import:legacy
    {file? : Path to the dump}
    {--queue= : Queue name}
    {--tag=* : Tags to apply}';

public function handle()
{
    $file  = $this->argument('file') ?: $this->promptForArgument('file');
    $queue = $this->option('queue') ?: $this->promptForOption('queue');
    $tags  = $this->option('tag') ?: $this->promptForArrayOption('tag'); // repeatable
    // ...
}

Base class for a family of internal tools. If you have several interactive ops commands, subclass the package's base command once and standardise your house prompts (confirmation wording, environment guard, output style) in it. This is also where you centralise the isInteractive() guard and the "am I allowed to prompt" check (see Gotchas).

Prompt-only-when-missing, never prompt-when-given. Every prompt should be ?:-guarded by the corresponding argument/option. This single rule is what makes the same command usable from a Makefile, a cron entry, and a human terminal — and it makes the new optional argument prompting in v4 harmless in automation.

Confirmation gate before side effects. Use the package's confirm prompt at the top of handle() and return 1 (not Command::FAILURE, which does not exist on Laravel 5.1) if declined or cancelled. Put all writes/dispatches after the last prompt so an Escape mid-wizard leaves nothing half-applied.

Wizard-style generator. Prompt → validate → build → $this->line('Wrote app/Services/Foo.php'). Pair the package with Laravel's own ->validate()/rules where the version supports it, or validate inside the prompt loop on 5.1 — signature-level validation of arguments runs before handle(), i.e. before any prompt can supply a value, so prompted values bypass it entirely.

Batch/repeatable options. Use the repeatable-option prompt for id lists, tags, or paths, and document the terminator (typically an empty line). Convert to a real array before use, and treat null vs [] vs [''] as distinct — you will hit all three.

Programmatic invocation. Anything calling Artisan::call() (controllers, jobs, other commands) must pass every value explicitly and --no-interaction, because there is no TTY there. Prefer wrapping such calls in a dedicated non-prompting command rather than branching inside the prompted one.

Laravel 11+ registration. Commands live in app/Console/Commands (auto-discovered) or are registered in routes/console.php / bootstrap/app.php via withCommands(); the old Kernel::$commands array is gone. If you ship a package, keep the 5.1-era service-provider commands([...]) for old versions and let discovery handle 11+.

Testing. $this->artisan('cmd')->expectsQuestion('Queue name?', 'default')->expectsOutput('...')->assertExitCode(0). Add a second test with ->run() after setting --no-interaction, and a third that simulates Escape (see Gotchas) asserting a non-zero exit code.

Gotchas and Tips

Non-interactive contexts are the #1 hazard. With v4, optional arguments now prompt when omitted. Any script, cron, Docker entrypoint, or Artisan::call() that previously relied on the silent default will now block or throw. Audit every caller, add --no-interaction explicitly, and make sure each prompted field has a real default. Consider guarding your own prompts with $this->input->isInteractive() so the command still runs headless.

Escape ≠ "empty answer". Cancellation is a distinct outcome from a blank/default answer. Assert on the exit code in tests and in shell (set -e plus $?), and never treat a null prompt result as "user accepted defaults". In tests, ESC is injected as the raw escape byte — "\e" or chr(27); if a test seems to hang, the byte probably was not accepted as a key press on that Symfony version.

Symfony Console version skew is the real compatibility axis, not the Laravel version. Laravel 5.1/5.2 ship Symfony Console 2.7; Laravel 9/10 ship 5.x/6.x; 11–13 ship 6.x/7.x. Key handling, hidden(), askWithCompletion(), choice defaults, and Command::SUCCESS/FAILURE constants all vary. If you claim the 5.1–13 range, run your CI matrix against at least one ancient and one current pair — that is exactly what v4.0.0 added (PHP 7.1/5.1 and PHP 8.5/13).

PHP 7.1 syntax ceiling if you extend or fork. No arrow functions (7.4), typed properties or ??= (7.4), constructor promotion/match/named args (8.0), enums/readonly/never (8.1), trailing commas in calls (7.3), or object type (7.2). And no str_contains/str_starts_with — that exact slip is what v4.0.0 fixed. If you are pinned to a 3.x tag on PHP 7.1–7.4, you are on a build that can fatal; upgrade.

Stale "recent commands" data. If the package keeps a command history/store, renaming or deleting commands can leave dangling entries. v4.0.0 handles stale entries gracefully, but after a refactor on an older pin, clear the store (cache/config path) to avoid odd suggestions or decode errors.

Validate after prompting, not before. Laravel validates $signature arguments when the command starts — before your prompts run. So prompted input is unvalidated by default: either validate in the prompt loop or re-validate the assembled values before acting.

Output interleaving. -vvv plus prompts can render confusingly; debug prompts with plain -v, and pipe a scripted stdin (printf 'value\n' | php artisan ...) when you need to see what the command actually received.

Container/TTY. Prompts need a real TTY. docker run without -it and any process with stdin redirected will not get key presses, so Escape handling silently never triggers. That is a config bug, not a package bug.

Extension points worth using. The prompt methods on the trait/base class are the intended override surface — subclass and override to add house defaults, an --yes fast path, or to read defaults from env/config() so CI can run the same command non-interactively. Also check for a config-published defaults file before hardcoding fallbacks in handle().

Upgrade note 3.x → 4.x. Optional- and flag-level prompting is new behaviour, so a command that used to fall through to a default now asks. That is effectively a soft breaking change for automation. Pin ^3.0 in CI-only scripts you cannot test, and bump deliberately.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package