@orieljs/framework 0.1.0-alpha.1 → 0.1.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/NOTICE +7 -0
  2. package/README.md +10 -1
  3. package/TRADEMARKS.md +33 -0
  4. package/dist/chunk-7ZQZYDOC.js +3253 -0
  5. package/dist/chunk-7ZQZYDOC.js.map +1 -0
  6. package/dist/{chunk-K6MAA3RM.js → chunk-GVNLZZTN.js} +7 -1
  7. package/dist/chunk-GVNLZZTN.js.map +1 -0
  8. package/dist/chunk-OXAATQA6.js +8433 -0
  9. package/dist/chunk-OXAATQA6.js.map +1 -0
  10. package/dist/{chunk-WI7E7Q46.js → chunk-VOYW3R5J.js} +8 -4
  11. package/dist/chunk-VOYW3R5J.js.map +1 -0
  12. package/dist/contracts.d.ts +841 -168
  13. package/dist/contracts.js +7 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +2916 -5178
  16. package/dist/index.js +7357 -2609
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-B8J6xq-7.d.ts → jsx-runtime-6um4Mwfb.d.ts} +99 -149
  19. package/dist/jsx-runtime.d.ts +3 -1
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/testing-CKwfWJGn.d.ts +1586 -0
  22. package/dist/testing.d.ts +3 -1408
  23. package/dist/testing.js +29 -5
  24. package/docs/authentication.md +487 -0
  25. package/docs/authorization.md +435 -0
  26. package/docs/cache.md +647 -0
  27. package/docs/collections.md +2270 -0
  28. package/docs/configuration.md +334 -0
  29. package/docs/console.md +652 -0
  30. package/docs/container.md +430 -0
  31. package/docs/controllers.md +406 -0
  32. package/docs/csrf.md +126 -0
  33. package/docs/database-testing.md +144 -0
  34. package/docs/database.md +236 -0
  35. package/docs/deployment.md +276 -0
  36. package/docs/eloquent-collections.md +218 -0
  37. package/docs/eloquent-factories.md +255 -0
  38. package/docs/eloquent-mutators.md +254 -0
  39. package/docs/eloquent-observers.md +145 -0
  40. package/docs/eloquent-relationships.md +557 -0
  41. package/docs/eloquent.md +494 -0
  42. package/docs/encryption.md +104 -0
  43. package/docs/errors.md +318 -0
  44. package/docs/events.md +336 -0
  45. package/docs/facades.md +278 -0
  46. package/docs/hashing.md +118 -0
  47. package/docs/helpers.md +1281 -0
  48. package/docs/http-tests.md +386 -0
  49. package/docs/inertia.md +485 -0
  50. package/docs/installation.md +209 -0
  51. package/docs/lifecycle.md +234 -0
  52. package/docs/live-tests.md +226 -0
  53. package/docs/live.md +910 -0
  54. package/docs/logging.md +255 -0
  55. package/docs/mail.md +619 -0
  56. package/docs/middleware.md +384 -0
  57. package/docs/migrations.md +416 -0
  58. package/docs/mocking.md +264 -0
  59. package/docs/navigation.json +273 -0
  60. package/docs/notifications.md +225 -0
  61. package/docs/pagination.md +247 -0
  62. package/docs/passwords.md +207 -0
  63. package/docs/providers.md +187 -0
  64. package/docs/queries.md +600 -0
  65. package/docs/queues.md +330 -0
  66. package/docs/requests.md +533 -0
  67. package/docs/responses.md +444 -0
  68. package/docs/routing.md +738 -0
  69. package/docs/scheduling.md +277 -0
  70. package/docs/seeding.md +122 -0
  71. package/docs/session.md +409 -0
  72. package/docs/starter-kits.md +396 -0
  73. package/docs/storage.md +192 -0
  74. package/docs/structure.md +264 -0
  75. package/docs/testing.md +165 -0
  76. package/docs/time-travel.md +138 -0
  77. package/docs/validation.md +1792 -0
  78. package/docs/verification.md +152 -0
  79. package/docs/views.md +566 -0
  80. package/docs/vite.md +322 -0
  81. package/package.json +35 -30
  82. package/dist/chunk-AI2RFMBT.js +0 -745
  83. package/dist/chunk-AI2RFMBT.js.map +0 -1
  84. package/dist/chunk-BZPCRKFQ.js +0 -2941
  85. package/dist/chunk-BZPCRKFQ.js.map +0 -1
  86. package/dist/chunk-K6MAA3RM.js.map +0 -1
  87. package/dist/chunk-WI7E7Q46.js.map +0 -1
@@ -0,0 +1,652 @@
1
+ # Oriel console
2
+
3
+ `oriel` is your app's command line. It runs the framework's commands, such as `migrate` and `make:model`, and the
4
+ commands you write for your own app.
5
+
6
+ ## Introduction
7
+
8
+ `oriel` is a script at the root of your app. Run it with Node, or with Bun. With no command, or with `list`, it lists
9
+ every command it knows:
10
+
11
+ ```sh
12
+ node oriel list
13
+ bun oriel list
14
+ ```
15
+
16
+ `list` takes a group's name, the part of a command's name before its `:`, to list only that group:
17
+
18
+ ```sh
19
+ node oriel list make
20
+ ```
21
+
22
+ Every command has a help screen, with its arguments and options. Give the command's name to `help`, or add `--help`
23
+ (or `-h`) to the command:
24
+
25
+ ```sh
26
+ node oriel help migrate
27
+ node oriel migrate --help
28
+ ```
29
+
30
+ A name `oriel` doesn't know is an error, with the names closest to it: "Command "migrat" is not defined.", then "Did
31
+ you mean one of these?" and up to three names.
32
+
33
+ On Node, the script loads your TypeScript with `tsx`, which a new app has among its dependencies; Bun runs it as it
34
+ is. Either way, it creates your app from `bootstrap/app.ts`, boots it, runs the command, and shuts the app down,
35
+ closing its database connections and writing out its logs, before it exits with the command's exit code.
36
+
37
+ ### Tinker (REPL)
38
+
39
+ `tinker` opens Node's REPL with your app in it, as `app`:
40
+
41
+ ```sh
42
+ node oriel tinker
43
+ ```
44
+
45
+ Type JavaScript at the `oriel>` prompt, `await` included. `.exit` (or Ctrl+D) leaves it.
46
+
47
+ > [!NOTE]
48
+ > Laravel's Tinker is PsySH, with your models and facades at hand and a config file of its own. Oriel's `tinker` is
49
+ > Node's own REPL, with `app` in its context and nothing else, and it has no settings.
50
+
51
+ ## Available commands
52
+
53
+ These are the commands every app has. A new app adds one of its own, `inspire`, in `routes/console.ts`.
54
+
55
+ | Command | What it does |
56
+ | --- | --- |
57
+ | `about` | Shows your app's name, the Oriel and Node versions, the environment, debug mode and URL |
58
+ | `help` | Shows a command's help screen |
59
+ | `list` | Lists the commands, all of them or one group's |
60
+ | `serve` | Serves your app on Oriel's server; `--watch` restarts it when your code changes |
61
+ | `test` | Runs your tests with Vitest |
62
+ | `tinker` | Opens Node's REPL with your app in it |
63
+ | `db:seed` | Seeds the database, with `DatabaseSeeder` or the seeder you name |
64
+ | `event:list` | Lists your events and their listeners |
65
+ | `ide:info` | Prints your app's routes, commands, config keys, `.env` names, views, Live components, rules and Inertia pages as JSON, for editors (the OrielJS extension for VS Code reads it); secret values are left out |
66
+ | `inertia:check-ssr` | Checks that the Inertia SSR server is up |
67
+ | `inertia:middleware` | Writes an Inertia middleware class, `HandleInertiaRequests` unless you name another |
68
+ | `inertia:start-ssr` | Starts the Inertia SSR server |
69
+ | `inertia:stop-ssr` | Stops the Inertia SSR server |
70
+ | `inertia:types` | Writes the TypeScript types of your Inertia pages' props |
71
+ | `install:api` | Writes `routes/api.ts` and adds it to `bootstrap/app.ts` |
72
+ | `key:generate` | Sets `APP_KEY` in `.env`; `--show` prints a key instead |
73
+ | `make:cast` | Writes a custom cast class in `app/Casts` |
74
+ | `make:command` | Writes a console command class in `app/Console/Commands` |
75
+ | `make:controller` | Writes a controller in `app/Http/Controllers` |
76
+ | `make:event` | Writes an event class in `app/Events` |
77
+ | `make:factory` | Writes a model factory in `database/factories` |
78
+ | `make:job` | Writes a queued job in `app/Jobs`; `--sync` writes one that runs at once |
79
+ | `make:listener` | Writes an event listener in `app/Listeners`; `--event` names its event, `--queued` queues it |
80
+ | `make:live` | Writes a Live component; `--test` writes its test too |
81
+ | `make:mail` | Writes a mailable in `app/Mail`, and its view |
82
+ | `make:middleware` | Writes a middleware class in `app/Http/Middleware` |
83
+ | `make:migration` | Writes a migration in `database/migrations`; `--create` or `--table` names its table |
84
+ | `make:model` | Writes a model in `app/Models`, and the files its options ask for |
85
+ | `make:notification` | Writes a notification in `app/Notifications` |
86
+ | `make:observer` | Writes a model observer in `app/Observers` |
87
+ | `make:policy` | Writes a policy in `app/Policies`; `--model` names its model |
88
+ | `make:provider` | Writes a service provider in `app/Providers` |
89
+ | `make:request` | Writes a form request in `app/Http/Requests` |
90
+ | `make:resource` | Writes a resource class, whose `toArray()` shapes a model for JSON, in `app/Http/Resources`; `--collection` writes one for a list |
91
+ | `make:rule` | Writes a validation rule object in `app/Rules` |
92
+ | `make:seeder` | Writes a seeder in `database/seeders` |
93
+ | `make:test` | Writes a test in `tests/Feature`, or in `tests/Unit` with `--unit` |
94
+ | `migrate` | Runs the migrations that haven't run yet |
95
+ | `migrate:fresh` | Drops every table and runs every migration again |
96
+ | `migrate:install` | Creates the table that records which migrations have run |
97
+ | `migrate:refresh` | Rolls back every migration and runs them all again |
98
+ | `migrate:reset` | Rolls back every migration |
99
+ | `migrate:rollback` | Rolls back the last batch of migrations |
100
+ | `migrate:status` | Shows whether each migration has run |
101
+ | `model:types` | Writes the TypeScript types of your models' attributes to `types/models.d.ts` |
102
+ | `queue:clear` | Deletes the jobs waiting on a queue |
103
+ | `queue:failed` | Lists the failed jobs |
104
+ | `queue:flush` | Deletes every failed job |
105
+ | `queue:forget` | Deletes one failed job |
106
+ | `queue:listen` | Runs the queue's jobs, each in a fresh process |
107
+ | `queue:restart` | Tells every queue worker to stop after its current job |
108
+ | `queue:retry` | Puts failed jobs back on their queue |
109
+ | `queue:work` | Runs the queue's jobs until you stop it |
110
+ | `route:list` | Lists your routes |
111
+ | `route:types` | Writes the TypeScript types of your route names and parameters, and the route table for your pages |
112
+ | `schedule:clear-cache` | Releases the locks that `withoutOverlapping()` holds |
113
+ | `schedule:interrupt` | Stops a running `schedule:run` from repeating its every-few-seconds tasks |
114
+ | `schedule:list` | Lists your scheduled tasks, with their cron expressions |
115
+ | `schedule:run` | Runs the scheduled tasks that are due |
116
+ | `schedule:work` | Runs the tasks that are due, every minute, until you stop it |
117
+ | `storage:link` | Links `public/storage` to `storage/app/public` |
118
+
119
+ `node oriel help <command>` lists a command's options. A few that are worth knowing up front:
120
+
121
+ - `make:model Flight -mcrfsa --policy` writes the model with every file it can have: its migration (`-m`), a resource
122
+ controller typed with the model (`-c`, `-r`), its factory (`-f`), its seeder (`-s`) and its policy (`--policy`).
123
+ `-a` (`--all`) asks for all of them, and for the `StoreFlightRequest` and `UpdateFlightRequest` form requests the
124
+ controller validates with. Short flags combine, as here.
125
+ - `make:controller` takes `--model=Photo`, `--invokable`, `--resource`, `--api` and `--requests`:
126
+ [Generating controllers](controllers.md#generating-controllers) shows what each one writes. A model whose name is a
127
+ reserved word gets a parameter with a `Model` suffix: `--model=Case` writes `show(caseModel: Case)`.
128
+ - `make:mail OrderShipped` writes the mailable and its view, `resources/views/mail/order-shipped.tsx`, which the
129
+ mailable renders as `mail.order-shipped`. `--view` names another view. A view that exists is left as it is, even
130
+ with `--force`.
131
+ - `serve --watch` restarts the server when a file it runs changes. [Running your
132
+ application](installation.md#running-your-application) says which files those are.
133
+ - A `make:` command doesn't replace a file that exists, unless you pass `--force`. `make:migration` writes a new file
134
+ every time, its name starting with the date and time.
135
+
136
+ The `schedule:` commands run the tasks you schedule in `routes/console.ts`. Your server's cron calls
137
+ `node oriel schedule:run` every minute; while you work, `node oriel schedule:work` does it for you.
138
+ [Task scheduling](scheduling.md) covers them.
139
+
140
+ ## Writing commands
141
+
142
+ Besides the framework's commands, you can write your own. They live in `app/Console/Commands`, where `oriel` finds
143
+ them by itself.
144
+
145
+ ### Generating commands
146
+
147
+ `make:command` writes a new command class in `app/Console/Commands`, and creates the folder if your app doesn't have
148
+ it yet:
149
+
150
+ ```sh
151
+ node oriel make:command SendEmails
152
+ ```
153
+
154
+ The class is named as you give it. Its signature is `app:` and the class's name in kebab case, here `app:send-emails`,
155
+ unless you pass another with `--command`:
156
+
157
+ ```sh
158
+ node oriel make:command SendEmails --command=mail:send
159
+ ```
160
+
161
+ ### Command structure
162
+
163
+ Once the command is written, set its `signature` and its `description`, which `list` and `help` show. The signature
164
+ is the command's name and the [input it expects](#defining-input-expectations). `handle()` runs when the command is
165
+ called.
166
+
167
+ Here is a command that sends a marketing email to a user. Its work is done by a service, which `handle()` takes from
168
+ the container with `inject()`:
169
+
170
+ ```ts title="app/Console/Commands/SendEmails.ts"
171
+ // @filename: app/Support/DripEmailer.ts
172
+ import type { User } from '../Models/User'
173
+ export declare class DripEmailer {
174
+ send(user: User): Promise<void>
175
+ }
176
+ // ---cut---
177
+ import { Command, inject } from '@orieljs/framework'
178
+ import { User } from '../../Models/User'
179
+ import { DripEmailer } from '../../Support/DripEmailer'
180
+
181
+ export class SendEmails extends Command {
182
+ readonly signature = 'mail:send {user}'
183
+ override description = 'Send a marketing email to a user'
184
+
185
+ async handle(): Promise<void> {
186
+ const user = await User.findOrFail(this.argument('user'))
187
+
188
+ await inject(DripEmailer).send(user)
189
+ }
190
+ }
191
+ ```
192
+
193
+ Each run of a command has its own scope, as a request does, so `inject()`, the helpers and the facades work inside
194
+ `handle()`, and a scoped service lasts for that one run.
195
+
196
+ > [!NOTE]
197
+ > Laravel injects the services that `handle()` type-hints. TypeScript types are gone when your app runs, so Oriel
198
+ > calls `handle()` with no arguments: take a service with `inject()` or its facade, inside `handle()`.
199
+
200
+ #### Exit codes
201
+
202
+ A command that returns nothing exits with `0`, which means success. Return a number to exit with another code:
203
+
204
+ ```ts
205
+ export class SendEmails extends Command {
206
+ readonly signature = 'mail:send {user}'
207
+
208
+ handle(): number {
209
+ this.error('Something went wrong.')
210
+
211
+ return 1
212
+ }
213
+ }
214
+ ```
215
+
216
+ A command that throws exits with `1`, and `oriel` prints the error with its stack.
217
+
218
+ > [!NOTE]
219
+ > Laravel's `$this->fail()` isn't in Oriel. Print the message with `this.error()` and return `1`, or throw.
220
+
221
+ ### Closure commands
222
+
223
+ A closure command is to a command class what a route's closure is to a controller. Define them in
224
+ `routes/console.ts` with `Artisan.command()`, which takes the signature and a function:
225
+
226
+ ```ts title="routes/console.ts"
227
+ import { Artisan } from '@orieljs/framework'
228
+ import type { CommandContext } from '@orieljs/framework/contracts'
229
+
230
+ Artisan.command('mail:send {user}', function (this: CommandContext, user: unknown) {
231
+ this.info(`Sending email to: ${String(user)}!`)
232
+ })
233
+ ```
234
+
235
+ The function receives the command's arguments, in the order the signature lists them, and its `this` is the command,
236
+ with a command class's methods for its input and output: `this.option()`, `this.info()`, `this.ask()` and the rest.
237
+ Write a `function`, not an arrow function, so that `this` is the command. The arguments are typed `unknown`: an argument
238
+ is a string, or a list of strings for an [array argument](#input-arrays).
239
+
240
+ > [!NOTE]
241
+ > Laravel matches a closure's parameters to the arguments and options by name, and injects any service it
242
+ > type-hints. Oriel passes the arguments only, by position: read an option with `this.option()`, and a service with
243
+ > `inject()`.
244
+
245
+ #### Closure command descriptions
246
+
247
+ `purpose()` gives a closure command its description, which `list` and `help` show:
248
+
249
+ ```ts
250
+ Artisan.command('mail:send {user}', function (this: CommandContext, user: unknown) {
251
+ // ...
252
+ }).purpose('Send a marketing email to a user')
253
+ ```
254
+
255
+ ### Isolatable commands
256
+
257
+ Laravel's `Isolatable` commands aren't in Oriel. To keep a command from running twice at the same time, take a
258
+ [cache lock](cache.md#atomic-locks) in its `handle()`, and return when another run holds it.
259
+
260
+ ## Defining input expectations
261
+
262
+ The signature says what your command takes from the command line: its name, then its arguments and options, each in
263
+ braces.
264
+
265
+ ### Arguments
266
+
267
+ This command takes one required argument, `user`:
268
+
269
+ ```ts
270
+ export class SendEmails extends Command {
271
+ readonly signature = 'mail:send {user}'
272
+
273
+ handle(): void {
274
+ // ...
275
+ }
276
+ }
277
+ ```
278
+
279
+ A `?` makes an argument optional, and `=` gives it a default:
280
+
281
+ ```text
282
+ mail:send {user?}
283
+ mail:send {user=foo}
284
+ ```
285
+
286
+ Run without a required argument, the command stops before `handle()`, with "Not enough arguments (missing:
287
+ "user")." and exit code `1`.
288
+
289
+ ### Options
290
+
291
+ An option is passed with two hyphens. One without a value is a switch: it is `true` when it is passed and `false`
292
+ when it isn't.
293
+
294
+ ```text
295
+ mail:send {user} {--queue}
296
+ ```
297
+
298
+ ```sh
299
+ node oriel mail:send 1 --queue
300
+ ```
301
+
302
+ #### Options with values
303
+
304
+ An `=` after the name makes an option take a value. When it isn't passed, it is `undefined`:
305
+
306
+ ```text
307
+ mail:send {user} {--queue=}
308
+ ```
309
+
310
+ ```sh
311
+ node oriel mail:send 1 --queue=default
312
+ node oriel mail:send 1 --queue default
313
+ ```
314
+
315
+ A value after the `=` is the default, used when the option isn't passed:
316
+
317
+ ```text
318
+ mail:send {user} {--queue=default}
319
+ ```
320
+
321
+ #### Option shortcuts
322
+
323
+ A letter and a `|` before the name give an option a shortcut, passed with one hyphen. The value follows the letter,
324
+ with no `=`:
325
+
326
+ ```text
327
+ mail:send {user} {--Q|queue=}
328
+ ```
329
+
330
+ ```sh
331
+ node oriel mail:send 1 -Qdefault
332
+ ```
333
+
334
+ Switch shortcuts combine: `-mfs` is `-m -f -s`.
335
+
336
+ An option the signature doesn't declare is an error: "The "--bogus" option does not exist.".
337
+
338
+ ### Input arrays
339
+
340
+ A `*` makes an argument take every value that follows it, as a list, so put it last:
341
+
342
+ ```text
343
+ mail:send {user*}
344
+ ```
345
+
346
+ ```sh
347
+ node oriel mail:send 1 2
348
+ ```
349
+
350
+ Here `user` is `['1', '2']`. With `?*`, the list may be empty:
351
+
352
+ ```text
353
+ mail:send {user?*}
354
+ ```
355
+
356
+ #### Option arrays
357
+
358
+ An option that takes several values is declared with `=*`. Pass it once for each value:
359
+
360
+ ```text
361
+ mail:send {--id=*}
362
+ ```
363
+
364
+ ```sh
365
+ node oriel mail:send --id=1 --id=2
366
+ ```
367
+
368
+ When it isn't passed, it is an empty list.
369
+
370
+ ### Input descriptions
371
+
372
+ Describe an argument or an option after a colon, with a space on each side. `help` shows the descriptions. A
373
+ signature can run over several lines:
374
+
375
+ ```ts
376
+ export class SendEmails extends Command {
377
+ readonly signature = `mail:send
378
+ {user : The ID of the user}
379
+ {--queue : Whether the job should be queued}`
380
+
381
+ handle(): void {
382
+ // ...
383
+ }
384
+ }
385
+ ```
386
+
387
+ > [!NOTE]
388
+ > Laravel can prompt for a missing required argument (`PromptsForMissingInput`). Oriel stops with an error instead:
389
+ > ask for it with [`ask()`](#prompting-for-input) when an optional argument is missing.
390
+
391
+ ## Command I/O
392
+
393
+ ### Retrieving input
394
+
395
+ `argument()` and `option()` read the command's input. `arguments()` and `options()` return all of them:
396
+
397
+ ```ts
398
+ declare const command: Command
399
+ // ---cut---
400
+ const userId = command.argument('user')
401
+
402
+ const all = command.arguments()
403
+
404
+ const queueName = command.option('queue')
405
+
406
+ const options = command.options()
407
+ ```
408
+
409
+ Arguments and option values are strings, as they arrive from the command line: `--tries=3` is `'3'`, so convert a
410
+ number with `Number()`. An argument is a string, a list for an array argument, or `undefined` when an optional one is
411
+ missing. An option is `true` or `false` for a switch, a string or `undefined` for an option with a value, and a list
412
+ for an option array. An argument or an option that the signature doesn't declare is `undefined`.
413
+
414
+ ### Prompting for input
415
+
416
+ `ask()` asks a question and returns the answer. A second argument is the default, shown in parentheses and returned
417
+ when the answer is empty:
418
+
419
+ ```ts
420
+ declare const command: Command
421
+ // ---cut---
422
+ const name = await command.ask('What is your name?')
423
+
424
+ const nameOrDefault = await command.ask('What is your name?', 'Taylor')
425
+ ```
426
+
427
+ `secret()` asks for a password or another secret:
428
+
429
+ ```ts
430
+ declare const command: Command
431
+ // ---cut---
432
+ const password = await command.secret('What is the password?')
433
+ ```
434
+
435
+ > [!WARNING]
436
+ > Laravel hides what the user types for `secret()`. Oriel's `secret()` reads the answer as `ask()` does, and the
437
+ > terminal shows it as it is typed.
438
+
439
+ #### Asking for confirmation
440
+
441
+ `confirm()` asks a yes or no question. It returns `true` when the answer is `y` or `yes`, in any case, and `false`
442
+ for any other answer:
443
+
444
+ ```ts
445
+ declare const command: Command
446
+ // ---cut---
447
+ if (await command.confirm('Do you wish to continue?')) {
448
+ // ...
449
+ }
450
+ ```
451
+
452
+ An empty answer gives the default, `false` unless you pass `true`:
453
+
454
+ ```ts
455
+ declare const command: Command
456
+ // ---cut---
457
+ if (await command.confirm('Do you wish to continue?', true)) {
458
+ // ...
459
+ }
460
+ ```
461
+
462
+ #### Multiple choice questions
463
+
464
+ `choice()` lists the choices, numbered from 0, and returns the one picked by its number or its text. The third
465
+ argument is the index of the default, returned for an empty answer:
466
+
467
+ ```ts
468
+ declare const command: Command
469
+ // ---cut---
470
+ const name = await command.choice('What is your name?', ['Taylor', 'Dayle'], 0)
471
+ ```
472
+
473
+ An answer that is neither stops the command, with ""Tom" is not a valid choice." and exit code `1`.
474
+
475
+ > [!NOTE]
476
+ > Laravel's `choice()` asks again after a wrong answer, and can take several answers; `anticipate()`, which suggests
477
+ > answers as you type, and Laravel Prompts aren't in Oriel.
478
+
479
+ ### Writing output
480
+
481
+ `info()`, `comment()` and `line()` print a line to the standard output, and `warn()` and `error()` print one to the
482
+ standard error. `newLine()` prints a blank line, or as many as you ask for:
483
+
484
+ ```ts
485
+ declare const command: Command
486
+ // ---cut---
487
+ command.info('The command was successful!')
488
+
489
+ command.error('Something went wrong!')
490
+
491
+ command.line('Display this on the screen')
492
+
493
+ command.newLine()
494
+ command.newLine(3)
495
+ ```
496
+
497
+ > [!NOTE]
498
+ > Laravel prints each kind of message in its own colour. Oriel prints them as plain text; `warn()` and `error()` go to
499
+ > the standard error, so a script that runs your command can tell them apart.
500
+
501
+ #### Tables
502
+
503
+ `table()` takes the column headings and the rows, each a list of strings, and sizes the columns to fit. The rows are
504
+ a plain array, so end a collection's `map()` with `all()`:
505
+
506
+ ```ts title="app/Console/Commands/ListUsers.ts"
507
+ import { Command } from '@orieljs/framework'
508
+ import { User } from '../../Models/User'
509
+
510
+ export class ListUsers extends Command {
511
+ readonly signature = 'users:list'
512
+ override description = 'List the users'
513
+
514
+ async handle(): Promise<void> {
515
+ const users = await User.all()
516
+
517
+ this.table(
518
+ ['Name', 'Email'],
519
+ users.map((user) => [user.name, user.email]).all(),
520
+ )
521
+ }
522
+ }
523
+ ```
524
+
525
+ Laravel's progress bars aren't in Oriel.
526
+
527
+ ## Registering commands
528
+
529
+ `oriel` registers every command class in `app/Console/Commands` and its subfolders: each exported class with a
530
+ `handle()` method and a `signature`. It then loads `routes/console.ts`, the file that `withRouting()` names as
531
+ `console` in `bootstrap/app.ts`, which defines your closure commands.
532
+
533
+ A command elsewhere is registered with `Artisan.register()`, in `routes/console.ts` or in a service provider's
534
+ `boot()`:
535
+
536
+ ```ts title="routes/console.ts"
537
+ // @filename: app/Domain/Orders/Commands/SendEmails.ts
538
+ import { Command } from '@orieljs/framework'
539
+ export class SendEmails extends Command {
540
+ readonly signature = 'orders:send-emails'
541
+ handle(): void {}
542
+ }
543
+ // ---cut---
544
+ import { Artisan } from '@orieljs/framework'
545
+ import { SendEmails } from '../app/Domain/Orders/Commands/SendEmails'
546
+
547
+ Artisan.register(SendEmails)
548
+ ```
549
+
550
+ A command class has no constructor arguments: `oriel` makes it with `new`. Two commands with the same name are one
551
+ command, the one registered last, so a command of yours can take the place of one of the framework's.
552
+
553
+ > [!WARNING]
554
+ > Your commands in `app/Console/Commands` and `routes/console.ts` are loaded only when your app runs in the console:
555
+ > under `oriel`, which `node oriel serve` is too, and in your tests. An app served by `public/index.ts` doesn't load
556
+ > them, so `Artisan.call()` from one of its routes can't find them. A command registered in a service provider's
557
+ > `boot()` is there in both.
558
+
559
+ ## Programmatically executing commands
560
+
561
+ `Artisan.call()` runs a command from your code, such as a route or a job. It takes the command's name and its
562
+ arguments and options, with each option's name written with its `--`, and returns the exit code:
563
+
564
+ ```ts title="routes/web.ts"
565
+ import { Artisan, Route } from '@orieljs/framework'
566
+
567
+ Route.post('/user/{user}/mail', async (user: string) => {
568
+ const exitCode = await Artisan.call('mail:send', { user, '--queue': 'default' })
569
+
570
+ return { exitCode, output: Artisan.output() }
571
+ })
572
+ ```
573
+
574
+ The command's output isn't printed: `Artisan.output()` returns what the last call printed, the standard output then
575
+ the standard error. A command that throws makes `Artisan.call()` throw, and so does a name that no command has.
576
+
577
+ > [!NOTE]
578
+ > Laravel's `Artisan::call()` also takes the whole command as one string, `'mail:send 1 --queue=default'`. Oriel's
579
+ > takes the name only, with the input as an object. `Artisan::queue()` isn't in Oriel: dispatch a
580
+ > [job](queues.md) that calls the command.
581
+
582
+ A command run by `Artisan.call()` has no one to answer its questions: `ask()`, `confirm()` and the others throw.
583
+
584
+ #### Passing array values
585
+
586
+ An option array or an array argument takes a list:
587
+
588
+ ```ts
589
+ await Artisan.call('mail:send', { '--id': [5, 13] })
590
+ ```
591
+
592
+ #### Passing boolean values
593
+
594
+ A switch takes `true` to pass it. `false` leaves it off:
595
+
596
+ ```ts
597
+ await Artisan.call('migrate:refresh', { '--force': true })
598
+ ```
599
+
600
+ ### Calling commands from other commands
601
+
602
+ Inside a command class, `this.call()` runs another command. Its output goes to your command's output, and it returns
603
+ the exit code:
604
+
605
+ ```ts title="app/Console/Commands/SendEmails.ts"
606
+ import { Command } from '@orieljs/framework'
607
+
608
+ export class SendEmails extends Command {
609
+ readonly signature = 'mail:send {user} {--queue=}'
610
+
611
+ async handle(): Promise<void> {
612
+ await this.call('mail:send-receipt', { user: this.argument('user'), '--queue': 'default' })
613
+ }
614
+ }
615
+ ```
616
+
617
+ > [!WARNING]
618
+ > `this.call()` with a name that no command has does nothing, and returns `undefined`. Laravel throws there. It lets
619
+ > a command call another that a part of the framework your app may not use would register, as `make:model -m` calls
620
+ > `make:migration`.
621
+
622
+ Laravel's `callSilently()` isn't in Oriel: call `Artisan.call()`, whose output isn't printed.
623
+
624
+ ## Testing commands
625
+
626
+ Your feature tests run a command with `artisan()`, and check its output and its exit code. Await the result: the
627
+ command runs then, and not before.
628
+
629
+ ```ts title="tests/Feature/SendEmails.test.ts"
630
+ // @filename: tests/TestCase.ts
631
+ import { TestCase as BaseTestCase } from '@orieljs/framework/testing'
632
+ export class TestCase extends BaseTestCase {}
633
+ // ---cut---
634
+ import { test } from 'vitest'
635
+ import { TestCase } from '../TestCase'
636
+
637
+ const t = TestCase.boot()
638
+
639
+ test('the mail is sent', async () => {
640
+ await t.artisan('mail:send', { user: 1 }).expectsOutput('Sending email to: 1!').assertExitCode(0)
641
+ })
642
+ ```
643
+
644
+ `expectsOutput()` wants a line of the output to equal its text, and `expectsOutputToContain()` the output to contain
645
+ it; `doesntExpectOutput()` and `doesntExpectOutputToContain()` want the opposite. `assertSuccessful()` and
646
+ `assertFailed()` check the exit code is `0`, or isn't.
647
+
648
+ > [!NOTE]
649
+ > Laravel's `expectsQuestion()` and `expectsConfirmation()` aren't in Oriel: a command that asks a question fails in
650
+ > a test. Give it the answer as an argument or an option instead.
651
+
652
+ The [Testing](testing.md) pages cover the rest.