@@ -485,48 +485,159 @@ leaves the CLI's defaults in place.
485485Registering a definition
486486------------------------
487487
488- Inside the CLI, a definition is registered with ` registerCommandDefinition ` :
488+ Inside the CLI, a definition is registered with ` registerCommand ` :
489489
490490``` ts
491- import { registerCommandDefinition } from " ../common/services/command-definition-adapter" ;
492- import addWidgetCommand from " ./add-widget" ;
491+ import { registerCommand } from " ../common/services/command-definition-adapter" ;
493492
494- registerCommandDefinition (addWidgetCommand );
493+ registerCommand ({
494+ name: " widget|add" ,
495+ options: { force: booleanOption ({ default: false }) },
496+ run : async (ctx ) => { … },
497+ });
498+ ```
499+
500+ It takes either a ` DefinedCommand ` — the result of ` defineCommand ` , marker and
501+ all — or the definition itself, which it defines on your behalf, so registering
502+ a command is one call. Either way the definition is validated before it reaches
503+ the registry. It claims every name the definition declares, through the
504+ ` CommandRegistry ` the target injector provides, and returns a
505+ ` DeferredCommandResult ` — see * The owner is ambient* below. The command instance
506+ is built by a factory on first resolution and cached.
507+
508+ Pass providers as the second argument to scope the command to a child injector
509+ of the one it registers against — how a definition is parameterized per
510+ registration:
511+
512+ ``` ts
513+ for (const [name, platform] of buildCommandPlatforms ) {
514+ registerCommand ({ ... buildCommandDefinition , name }, [
515+ { provide: BUILD_PLATFORM , useValue: platform },
516+ ]);
517+ }
518+ ```
519+
520+ That is how one definition serves several commands that differ only in data —
521+ the platform each one targets — instead of one command subclassing another.
522+
523+ ** Which injector it registers against is not a parameter.** It is the injector
524+ of the current injection context — see * The owner is ambient* below — and the
525+ CLI's own injector outside one. To register against some other injector, run
526+ the call in its context:
527+
528+ ``` ts
529+ runInInjectionContext (someInjector , () => registerCommand (definition ));
495530```
496531
497- It takes a ` DefinedCommand ` — the result of ` defineCommand ` , marker and all —
498- and rejects a bare object of the right shape, so a definition can never reach
499- the registry without having been validated. It registers under every name the
500- definition declares, through the ` CommandRegistry ` the target injector provides;
501- pass a second argument to target a different injector (tests do this). The
502- command instance is built by a factory on first resolution and cached.
532+ A test registering into its own container does that too, which is the same
533+ path the CLI itself takes.
503534
504- ` registerCommandDefinition ` lives in
535+ ` registerCommand ` lives in
505536` lib/common/services/command-definition-adapter ` rather than in
506537` nativescript/contracts ` , because it reaches into the CLI runtime — the
507538side-effect-free contracts entry point deliberately does not pull it in.
508539` defineCommand ` , the option helpers and all the types are exported from both
509540` nativescript/contracts ` and ` lib/common/define-command ` .
510541
511- Extensions do not need ` registerCommandDefinition ` at all: a
542+ Extensions do not need ` registerCommand ` at all: a
512543` nativescript.commands ` manifest entry may point straight at a module that
513544exports a definition, and the CLI adapts and registers it lazily under the
514545manifest key (see [ extensions.md] ( extensions.md ) ).
515546
547+ ### Registering lazily
548+
549+ ` registerCommand ` needs the definition in hand, which means loading the module
550+ that holds it. ` registerLazyCommand ` claims the name instead, and loads the
551+ module the first time that one command is resolved:
552+
553+ ``` ts
554+ import { registerLazyCommand } from " ../common/services/command-definition-adapter" ;
555+
556+ registerLazyCommand <typeof import (" ./commands/run" ).iosRunCommand >(
557+ " run|ios" ,
558+ () => require (" ./commands/run" ).iosRunCommand ,
559+ );
560+ ```
561+
562+ The name routes immediately — including through the ` run ` dispatcher the CLI
563+ synthesizes for it — so listing commands, resolving a sibling, or printing help
564+ for the parent never loads ` run.js ` . The loader runs on the resolution of
565+ ` run|ios ` alone, and what it returns is registered under the name that was
566+ claimed.
567+
568+ ** The type argument is mandatory.** ` require() ` is typed ` any ` , so nothing can
569+ be inferred from the loader: without the type argument the name would be
570+ checked against nothing at all. Leave it off and the ` name ` parameter says so:
571+
572+ ```
573+ error TS2345: Argument of type '"run|ios"' is not assignable to parameter of type
574+ '"Pass the definition type: registerLazyCommand<typeof import('./commands/x').cmd>(...)"'
575+ ```
576+
577+ With the type argument, the name is checked against the one the definition
578+ declares — every one of them, for a definition that declares aliases:
579+
580+ ```
581+ error TS2345: Argument of type '"run|iosss"' is not assignable to parameter of
582+ type '"run|ios"'
583+ ```
584+
585+ and a type argument that is not a definition is rejected against the
586+ constraint. The loader is re-checked at runtime as well, because the guarantee
587+ is only as good as the type the call site passed.
588+
589+ ** The loader must be synchronous.** ` CommandsService ` reads the resolved
590+ command's ` dashedOptions ` before it validates the command line, so a command
591+ that is still being imported has no options to validate against — a dynamic
592+ ` import() ` here would report every flag as unknown. ` require ` is the tool for
593+ this job.
594+
595+ ** Providers are optional and cost nothing until the command runs.** The child
596+ injector is built inside the loader, so a name that is never resolved never
597+ creates one:
598+
599+ ``` ts
600+ registerLazyCommand <typeof import (" ./commands/x" ).cmd >(
601+ " x" ,
602+ () => require (" ./commands/x" ).cmd ,
603+ [{ provide: SOME_TOKEN , useValue: " value" }],
604+ );
605+ ```
606+
607+ ** The owner is ambient.** Every registration has an owner, which attributes
608+ conflicts and load failures and makes re-registering the same name under the
609+ same owner a no-op instead of a conflict. It is not a parameter: the helper
610+ targets the injector of the current injection context when there is one, and
611+ reads ` COMMAND_OWNER ` off it. Outside a context it targets the CLI's own
612+ injector, and the CLI is the owner. An extension's module is loaded inside a
613+ context whose injector provides ` COMMAND_OWNER ` , so a command the module
614+ registers on its own is attributed to the extension without the module naming
615+ itself — through ` registerCommand ` just as much as through this helper.
616+
617+ ` registerCommand ` therefore returns a ` DeferredCommandResult ` too: every
618+ registration is arbitrated against the names already claimed, rather than
619+ overwriting one.
620+
621+ ** Conflicts are returned, not thrown.** The result is the same
622+ ` DeferredCommandResult ` the extension manifest path gets — `{ registered:
623+ true }` , or ` registered: false` with a ` rejection` to branch on. The CLI's own
624+ bootstrap wraps the call and throws, because a name it cannot claim is a
625+ mistake in ` bootstrap.ts ` ; a host loading someone else's command usually wants
626+ to warn and carry on. ` describeRejection(rejection) ` renders one for a human.
627+
516628### One definition, several registrations
517629
518630A family of commands that differ only in a value — ` run|android ` and ` run|ios ` ,
519- say — is one definition registered several times, each against a child injector
520- that provides the value:
631+ say — is one definition registered several times, each with providers that
632+ carry the value:
521633
522634``` ts
523635const PLATFORM = new InjectionToken <string >(" commandPlatform" );
524636
525637for (const platform of [" android" , " ios" ]) {
526- registerCommandDefinition (
527- { ... definition , name: ` run|${platform } ` },
528- injector .createChild ([{ provide: PLATFORM , useValue: platform }]),
529- );
638+ registerCommand ({ ... definition , name: ` run|${platform } ` }, [
639+ { provide: PLATFORM , useValue: platform },
640+ ]);
530641}
531642```
532643
0 commit comments