Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Support extracting options/args types from Command. #589

Open
NfNitLoop opened this issue Apr 30, 2023 · 4 comments
Open

Feature: Support extracting options/args types from Command. #589

NfNitLoop opened this issue Apr 30, 2023 · 4 comments

Comments

@NfNitLoop
Copy link

Context

For complicated CLIs, there can be lots of .action(…)s, and having your command implementation bodies indented inside of the Command builder is not always ideal. Even for small projects, I prefer this pattern which decouples my implementation from the arg parsing:

const COMMAND = new Command()
    // etc, global setup.
    

async function subCommand(options: MyOptions, args: [string, etc]) {
    // …
}

COMMAND.command("subcommand")
    // options
    .action(subCommand)

But to do this as of now (unless I'm missing something? 😅) I have to define my own Options type which conforms to the options that Command.action() will pass me.

Feature request

It would be nice if Command supported an "infer" like Zod.

In Zod, you do something like:

type A = z.infer<typeof A>;

Maybe in Cliffy's Command, we could do something like:

const SUBCOMMAND = COMMAND.command("subcommand")
    // options
    .action(subCommand)

type Options = Command.inferOptions<typeof SUBCOMMAND>
type Args = Command.inferArguments<typeof SUBCOMMAND>

The implementation of z.infer is not something that I've looked into, so I'm not sure how easy it would be to apply to Command. But as a user it would be nice to have. 😊 Thanks!

@c4spar
Copy link
Owner

c4spar commented May 20, 2023

Hi @NfNitLoop, sry for late replay, i'm on a long trip currently.
I think this is a good idea. We have already Type.infer which works similarly. One way of implementing inferOptions and inferArguments could be to extract the types from the action handler.

@xe54ck
Copy link

xe54ck commented Oct 28, 2023

Is there currently any way to extract the action handler types? I am currently running into a similar issue when using a globalOption. I can define types manually but this becomes tedious if you have many options.

For example, in main entry:

await new Command()
	.globalOption('-p, --project-path <project-path:file>', 'Path to project folder', {
		default: config.projectPath,
	})
	.command('init', Init)
	.parse(Deno.args)

And init.ts in a different file:

export const Init = new Command()
	.description('init')
	.action(async (options) => {
		//  This works but type error
		// `Property 'projectPath' does not exist on type 'void`
		const { projectPath } = options
	})

@DrakeTDL
Copy link
Contributor

DrakeTDL commented Oct 29, 2023

@xe54ck You can use the infer type.

For example, you can do something like this:

entry.ts

export type GlobalOptions = typeof args extends
  Command<void, void, void, [], infer Options extends Record<string, unknown>>
  ? Options
  : never;

const args = new Command()
  .globalOption(
    "-p, --project-path <project-path:file>",
    "Path to project folder",
    {
      default: config.projectPath,
    },
  );

await args
  .command("init", Init)
  .parse();

init.ts

import type { GlobalOptions } from "./entry.ts";

export const Init = new Command<GlobalOptions>()
  .description("init")
  .action(async (options) => {
    //  This works but type error
    // `Property 'projectPath' does not exist on type 'void`
    const { projectPath } = options;
  });

@timharek
Copy link

Thank you, @DrakeTDL! That worked great, been looking for something like this for a while! 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants