Skip to content

[bug] Tauri CLI does not support more than one subcommand #4688

@AlexanderAni

Description

@AlexanderAni

Describe the bug

Overview

Tauri CLI does not support more than one subcommand in command line.
Current CLAP implementation supports multiple subcommands.

Example of multiple subcommands:

git stash push

Where:

  • git — command
  • stash — first subcommand (relative to git)
  • push — second subcommand (relative to stash)

Reproduction

1. Create Tauri App

I prefer to use Vite to bootstrap Tauri App.

yarn create vite

2. Add CLI commands to Tauri config

Modify tauri.conf.json to add git stash push subcommands.

cat tauri.conf.json
{
    "tauri": {
        "cli": {
            "description": "Command example `git stash push`",
            "subcommands": {
                "git": {
                    "subcommands": {
                        "stash": {
                            "subcommands": {
                                "push": {}
                            }
                        }
                    }
                }
            }
        }
    }
}

Note: only cli part of config provided.

3. Add output on startup

This code will print raw matches and a list of subcommands in Terminal.

cat main.rs
use tauri::api::cli::Matches;

fn main() {
    let context = tauri::generate_context!();
    tauri::Builder::default()
        .setup(|app| {
            match app.get_cli_matches() {
                Ok(matches) => {
                    println!("{:?}", matches);
                    println!("Subcommands:");
                    show_subcommands(matches);
                },
                Err(_) => panic!{"CLI Parsing Error"}
            };
            Ok(())
        })
        .menu(tauri::Menu::os_default(&context.package_info().name))
        .run(context)
        .expect("Error while running Tauri application.");
}

fn show_subcommands(matches: Matches) {
    match matches.subcommand {
        Some(command) => {
            println!("\t`{}`", command.name);
            show_subcommands(command.matches);
        },
        None => {}
    };
}

4. Check the results

You can check the results without building an application.

cargo tauri dev git stash push

Expected behavior

Current Result:

Matches { args: {}, subcommand: Some(SubcommandMatches { name: "git", matches: Matches { args: {}, subcommand: None } }) }
Subcommands:
    `git`
  • Extracted only 1 subcommand git instead of 3 subcommands (git, stash, push).

Expected Result:

Matches { args: {}, subcommand: Some(SubcommandMatches { name: "git", matches: Matches { args: {}, subcommand: Some(SubcommandMatches { name: "stash", matches: Matches { args: {}, subcommand: Some(SubcommandMatches { name: "push", matches: Matches { args: {}, subcommand: None } }) } }) } }) }
Subcommands:
    `git`
    `stash`
    `push`
  • Extracted all 3 subcommands: git, stash, push.

Platform and versions

Environment
  › OS: Linux Mint 20.3 X64
  › Node.js: 18.5.0
  › npm: 8.12.1
  › pnpm: Not installed!
  › yarn: 1.22.17
  › rustup: Not installed!
  › rustc: 1.62.0
  › cargo: 1.62.0
  › Rust toolchain: 

Packages
  › @tauri-apps/cli [NPM]: 1.0.0
  › @tauri-apps/api [NPM]: 1.0.1
  › tauri [RUST]: 1.0.4,
  › tauri-build [RUST]: 1.0.4,
  › tao [RUST]: 0.12.1,
  › wry [RUST]: 0.19.0,

App
  › build-type: bundle
  › CSP: unset
  › distDir: ../dist
  › devPath: http://localhost:3000/

App directory structure
  ├─ src
  ├─ node_modules
  └─ src-tauri

Stack trace

not applicable

Additional context

Reference: Current Source code

Current commit: 7386084.

fn get_matches_internal(config: &CliConfig, matches: &ArgMatches) -> Matches {
    let mut cli_matches = Matches::default();
    map_matches(config, matches, &mut cli_matches);

    if let Some((subcommand_name, subcommand_matches)) = matches.subcommand() {
        let mut subcommand_cli_matches = Matches::default();
        map_matches(
            config.subcommands().unwrap().get(subcommand_name).unwrap(),
            subcommand_matches,
            &mut subcommand_cli_matches,
        );
        cli_matches.set_subcommand(subcommand_name.to_string(), subcommand_cli_matches);
    }

    cli_matches
}

fn map_matches(config: &CliConfig, matches: &ArgMatches, cli_matches: &mut Matches) {
    if let Some(args) = config.args() {
        for arg in args {
            #[allow(deprecated)]
            let occurrences = matches.occurrences_of(arg.name.clone());
            let value = if occurrences == 0 || !arg.takes_value {
                Value::Bool(occurrences > 0)
            } else if arg.multiple {
                #[allow(deprecated)]
                matches
                    .values_of(arg.name.clone())
                    .map(|v| {
                        let mut values = Vec::new();
                        for value in v {
                            values.push(Value::String(value.to_string()));
                        }
                        Value::Array(values)
                    })
                    .unwrap_or(Value::Null)
            } else {
                #[allow(deprecated)]
                matches
                    .value_of(arg.name.clone())
                    .map(|v| Value::String(v.to_string()))
                    .unwrap_or(Value::Null)
            };

            cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences });
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions