@@ -11,7 +11,8 @@ import { injector } from "../yok";
1111import { IExtensibilityService } from "../definitions/extensibility" ;
1212import { IGoogleAnalyticsPageviewData } from "../definitions/google-analytics" ;
1313import { CommandsService as CommandsServiceContract } from "../contracts/commands-service" ;
14- import { CommandReference , commandNameOf } from "../define-command" ;
14+ import { CommandReference , toCommandDefinition } from "../define-command" ;
15+ import { createCommandFromDefinition } from "./command-definition-adapter" ;
1516import {
1617 ICommandParameter ,
1718 ICommand ,
@@ -247,24 +248,28 @@ export class CommandsService
247248 * `checkConsent` may prompt on a terminal the caller has put in raw mode.
248249 */
249250 public async runCommand (
250- command : CommandReference ,
251+ reference : CommandReference ,
251252 commandArguments : string [ ] = [ ] ,
252253 ) : Promise < void > {
253- const commandName = commandNameOf ( command ) ;
254+ // Known before the lookup, so a failure to resolve reports under the name
255+ // the caller used.
256+ let commandName = typeof reference === "string" ? reference : undefined ;
254257 this . inProcessDepth ++ ;
255258 try {
256- const command = this . $injector . resolveCommand ( commandName ) ;
257- if ( ! command ) {
258- this . $errors . failWithHelp (
259- `Unknown command '${ helpers . stringReplaceAll ( commandName , "|" , " " ) } '.` ,
260- ) ;
261- }
259+ const resolved = this . resolveReference ( reference ) ;
260+ const command = resolved . command ;
261+ commandName = resolved . commandName ;
262262
263263 this . commands . push ( { commandName, commandArguments } ) ;
264264 const restoreOptions = this . primeOptions ( command ) ;
265265 try {
266266 if (
267- ! ( await this . canExecuteResolvedCommand ( commandName , commandArguments ) )
267+ ! ( await this . canExecuteResolvedCommand (
268+ commandName ,
269+ commandArguments ,
270+ undefined ,
271+ command ,
272+ ) )
268273 ) {
269274 let commandWithArgs = commandName ;
270275 if ( commandArguments && commandArguments . length ) {
@@ -301,25 +306,21 @@ export class CommandsService
301306 * reuse another's precondition without importing its handlers.
302307 */
303308 public async canExecuteCommand (
304- command : CommandReference ,
309+ reference : CommandReference ,
305310 commandArguments : string [ ] = [ ] ,
306311 ) : Promise < boolean > {
307- const commandName = commandNameOf ( command ) ;
308312 this . inProcessDepth ++ ;
309313 try {
310- const command = this . $injector . resolveCommand ( commandName ) ;
311- if ( ! command ) {
312- this . $errors . failWithHelp (
313- `Unknown command '${ helpers . stringReplaceAll ( commandName , "|" , " " ) } '.` ,
314- ) ;
315- }
314+ const { commandName, command } = this . resolveReference ( reference ) ;
316315
317316 this . commands . push ( { commandName, commandArguments } ) ;
318317 const restoreOptions = this . primeOptions ( command ) ;
319318 try {
320319 return await this . canExecuteResolvedCommand (
321320 commandName ,
322321 commandArguments ,
322+ undefined ,
323+ command ,
323324 ) ;
324325 } finally {
325326 restoreOptions ( ) ;
@@ -352,6 +353,42 @@ export class CommandsService
352353 * and the host keeps reading the replacement long after the command is
353354 * done. An in-process dispatch has to put the parser back where it found it.
354355 */
356+ /**
357+ * A name is looked up in the registry; a definition or class is run as the
358+ * caller holds it, registered or not, so what runs is what was referenced.
359+ * Its first name still identifies it for hooks and reporting.
360+ */
361+ private resolveReference ( reference : CommandReference ) : {
362+ commandName : string ;
363+ command : ICommand ;
364+ } {
365+ if ( typeof reference === "string" ) {
366+ const command = this . $injector . resolveCommand ( reference ) ;
367+ if ( ! command ) {
368+ this . $errors . failWithHelp (
369+ `Unknown command '${ helpers . stringReplaceAll ( reference , "|" , " " ) } '.` ,
370+ ) ;
371+ }
372+
373+ return { commandName : reference , command } ;
374+ }
375+
376+ const definition = toCommandDefinition ( reference ) ;
377+ if ( ! definition ) {
378+ throw new Error (
379+ "Expected a command name, a defineCommand() definition or a " +
380+ "Command() class to run." ,
381+ ) ;
382+ }
383+
384+ return {
385+ commandName : Array . isArray ( definition . name )
386+ ? definition . name [ 0 ]
387+ : definition . name ,
388+ command : createCommandFromDefinition ( definition , < any > this . $injector ) ,
389+ } ;
390+ }
391+
355392 private primeOptions ( command : ICommand ) : ( ) => void {
356393 if ( command . isHierarchicalCommand ) {
357394 return ( ) => undefined ;
@@ -375,8 +412,9 @@ export class CommandsService
375412 commandName : string ,
376413 commandArguments : string [ ] ,
377414 isDynamicCommand ?: boolean ,
415+ resolved ?: ICommand ,
378416 ) : Promise < boolean > {
379- const command = this . $injector . resolveCommand ( commandName ) ;
417+ const command = resolved || this . $injector . resolveCommand ( commandName ) ;
380418 const beautifiedName = helpers . stringReplaceAll ( commandName , "|" , " " ) ;
381419 if ( command ) {
382420 // Verify command is enabled
0 commit comments