@@ -346,7 +346,8 @@ The run context
346346 ` const { args, arguments } = ctx ` would not even parse.
347347- ` ctx.options ` — the current value of each declared option, read at the moment
348348 the command executes.
349- - ` ctx.injector ` — the injector this command was registered against; see
349+ - ` ctx.injector ` — this invocation's injector, a child of the one the command
350+ was registered against; see
350351 [ Injection, and the first ` await ` ] ( #injection-and-the-first-await ) .
351352- ` ctx.fail(message) ` — fails the command with ` message ` and a usage help
352353 suggestion.
@@ -416,10 +417,13 @@ async run(ctx) {
416417` ctx.injector ` is deliberately the injector itself rather than a bound
417418` ctx.inject(...) ` : it is a visibly different mechanism because it obeys
418419different rules, and mistaking one for the other is exactly the bug this shape
419- prevents. It is the injector the command was ** registered against** , so it also
420- resolves providers a child scope supplied — see
421- [ Registering a definition] ( #registering-a-definition ) . The same guidance, and
422- the reasoning behind it, is in ` dependency-injection.md ` .
420+ prevents. It is the ** invocation's own injector** : a child of the one the
421+ command was registered against, holding the context under ` COMMAND_CONTEXT `
422+ and any per-command providers — see
423+ [ Registering a definition] ( #registering-a-definition ) . ` inject() ` before the
424+ first ` await ` and ` ctx.injector.get() ` after it are therefore the same lookup
425+ against the same injector. The same guidance, and the reasoning behind it, is
426+ in ` dependency-injection.md ` .
423427
424428Where a handler gets its services
425429---------------------------------
@@ -466,8 +470,8 @@ Sharing is either of two things, and neither of them is a bag:
466470 }
467471 ```
468472
469- - ** A whole command's precondition** — ` canExecuteCommand(name, args) ` , which
470- asks that command itself; see [ Asking another
473+ - ** A whole command's precondition** — ` CommandsService. canExecuteCommand` ,
474+ which asks that command itself; see [ Asking another
471475 command] ( #asking-another-command ) .
472476
473477### ` setup ` , when a command has one
@@ -600,8 +604,10 @@ schema does not declare is a compile error. `this.context` also carries
600604** Per-command providers see the invocation.** The context is provided to the
601605invocation's own child injector under the ` COMMAND_CONTEXT ` token, which is how
602606the base class reads it. A provider registered for one command — through the
603- ` providers ` argument of ` registerCommand ` or ` registerLazyCommand ` — can inject
604- it too, and resolves nothing outside a running invocation.
607+ ` providers ` argument of ` registerCommand ` or ` registerLazyCommand ` — lives in
608+ that same child, so a factory or class among them can inject the context too.
609+ The cost is that such a provider is built once per invocation, never shared
610+ across invocations, and resolves nothing outside a running one.
605611
606612** One field per dependency.** Each service the class uses is its own field,
607613read as ` this.$x ` :
@@ -681,8 +687,8 @@ the registry. It claims every name the definition declares, through the
681687` DeferredCommandResult ` — see _ The owner is ambient_ below. The command instance
682688is built by a factory on first resolution and cached.
683689
684- Pass providers as the second argument to scope the command to a child injector
685- of the one it registers against — how a definition is parameterized per
690+ Pass providers as the second argument to add them to each invocation's child
691+ injector, the one ` ctx.injector ` names — how a definition is parameterized per
686692registration:
687693
688694``` ts
@@ -827,14 +833,24 @@ per-platform command subclasses a shared base to override one field.
827833Running a command in process
828834----------------------------
829835
830- ` runCommand ` dispatches a registered command from inside the process that is
831- already running:
836+ The ` CommandsService ` contract dispatches a registered command from inside the
837+ process that is already running. It is a service like any other, so it follows
838+ the rule every service does: ` inject() ` before the first ` await ` , the
839+ injector after it, and a key shortcut's action reaches it through the
840+ injector its context carries:
832841
833842``` ts
834- import { runCommand } from " ../common/services/command-definition-adapter " ;
843+ import { CommandsService } from " nativescript/contracts " ;
835844
836- await runCommand (" open|ios" );
837- await runCommand (" install" , [" lodash" ]);
845+ // in a class command
846+ private $commandsService = inject (CommandsService );
847+ await this .$commandsService .runCommand (" autocomplete" );
848+
849+ // in an inline handler, after the first await
850+ await ctx .injector .get (CommandsService ).runCommand (" install" , [" lodash" ]);
851+
852+ // in a shortcut action
853+ action : (ctx ) => ctx .injector .get (CommandsService ).runCommand (" open|ios" ),
838854```
839855
840856The command gets what a typed command line gives it, in the same order: its
@@ -852,29 +868,56 @@ running afterwards:
852868 the caller decides what happens next.
853869- ** Analytics do not fire.** An in-process dispatch is not a new invocation of
854870 the CLI, and the consent check can prompt on a terminal the caller has put
855- into raw mode. Hooks do fire: a project's ` before-open-ios ` hook is part of
856- what ` open|ios ` means, however the command was reached.
871+ into raw mode. Hooks do fire, under the same names the command line fires:
872+ ` open|ios ` fires ` before-open-ios ` and then ` before-open ` (and ` after-open ` ,
873+ ` after-open-ios ` on the way out), however the command was reached.
857874
858875The options service is put back the way it was found. Merging a command's
859876declarations into it rewrites the values the host process is still running on
860877— ` open|ios ` declares ` watch: false ` , which would otherwise leave an ` ns start `
861878out of watch mode for the rest of its life.
862879
863- Which injector it dispatches through follows the rule ` registerCommand ` does:
864- the injector of the current injection context, and the CLI's own outside one.
865- ` runCommand ` is a thin call onto ` CommandsService.executeCommandInProcess ` ,
866- where the pipeline itself lives.
880+ In-process dispatches nest; they never overlap. The options are put back in
881+ the order the dispatches were entered, which only restores the right values
882+ when each one finishes before the dispatch it was started from. A dispatch
883+ started while another is in flight, and not from inside it — two
884+ ` runCommand ` calls under one ` Promise.all ` , say — is rejected with
885+ `Cannot dispatch '…' in process while '…' is still running: in-process
886+ dispatches must nest, not overlap; await the running one first.` Await one
887+ before starting the next.
888+
889+ There is deliberately no free ` runCommand() ` function: one that silently fell
890+ back to the CLI's root injector outside an injection context would dispatch
891+ through the wrong scope from exactly the places — after an ` await ` , inside a
892+ stdin handler — where the mistake is hardest to notice. The injector you hold
893+ is the one to dispatch through.
867894
868895### Asking another command
869896
870- ` canExecuteCommand(name, args) ` asks a registered command whether it * could*
871- run, without running it:
897+ Both methods take a registered name, or — the typed way — a definition or
898+ ` Command() ` class. A name is looked up in the registry, and a parent name is
899+ routed to its subcommand the way the command line routes it —
900+ ` runCommand("device") ` runs ` device|*list ` , ` runCommand("device", ["log"]) `
901+ runs ` device|log ` ; a definition runs as
902+ given, whether or not it is registered, so ` runCommand(prepareCommandDefinition) `
903+ runs exactly what you hold and cannot go stale the way a string can. Its first
904+ name still identifies it for hooks and reporting.
905+
906+ ` CommandsService.canExecuteCommand(command, args) ` asks a registered command
907+ whether it * could* run, without running it:
872908
873909``` ts
874- import { canExecuteCommand } from " ../common/services/command-definition-adapter" ;
910+ import { CommandsService } from " nativescript/contracts" ;
911+
912+ private $commandsService = inject (CommandsService );
875913
876914async canExecute (): Promise < boolean > {
877- if (!(await canExecuteCommand (" prepare" , [this .args [0 ]]))) {
915+ if (
916+ !(await this.$commandsService.canExecuteCommand(
917+ prepareCommandDefinition ,
918+ [this .args [0 ]],
919+ ))
920+ ) {
878921 return false ;
879922 }
880923
@@ -886,18 +929,18 @@ This is how one command builds on another's precondition. `embed` prepares the
886929project, so "could ` embed ` run" starts with "could ` prepare ` run" — and the way
887930to ask that is to ask ` prepare ` , not to import its ` canExecute ` and hand it
888931services. The named command is resolved and its options primed exactly as
889- ` runCommand ` does, then its own ` canExecute ` returns the verdict. It builds its
890- own setup from its own services; nothing crosses between the two commands but
891- the name and the arguments.
932+ ` runCommand ` does, then its own ` canExecute ` returns its verdict or throws. It
933+ builds its own setup from its own services; nothing crosses between the two
934+ commands but the name and the arguments.
892935
893936Pass only the arguments the child's own ` arguments ` policy accepts. The child
894937enforces that policy before its ` canExecute ` , so forwarding a caller's whole
895938argument list to a child that declares fewer is a rejection, not a wider check.
896939
897- ` canExecuteCommand ` is a thin call onto
898- ` CommandsService.canExecuteCommandInProcess ` , and follows ` runCommand ` in
899- everything else: the same injector rule, the same option priming and
900- restoration .
940+ ` canExecuteCommand ` follows ` runCommand ` in everything else: the same option
941+ priming and restoration, the same routing of a parent name to its subcommand.
942+ The deprecated ` canExecuteCommandInProcess ` and ` executeCommandInProcess `
943+ call the two methods with a name .
901944
902945### Key shortcuts
903946
@@ -910,7 +953,7 @@ an `action` that runs it:
910953 key : " I" ,
911954 description : " Open project in Xcode" ,
912955 when : onPlatform (" iOS" ),
913- action : () => runCommand (" open|ios" ),
956+ action : (ctx ) => ctx . injector . get ( CommandsService ). runCommand (" open|ios" ),
914957}
915958```
916959
0 commit comments