Skip to content

dotnet CLI: Add --cli-schema option for CLI structure JSON #49118

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

Merged
merged 17 commits into from
Jun 15, 2025

Conversation

MiYanni
Copy link
Member

@MiYanni MiYanni commented May 22, 2025

Resolves: #46345

Summary

This PR is to add the --cli-schema option to every command. This option will defer the operation of the command entirely, similar to --help. When --cli-schema is provided, the JSON representation of that command is returned. Currently, I'm keeping the --cli-schema option as hidden as the internal structure of the CLI has many 'thorny edges' to it, and would need some massaging prior to making this option public. Plus, the actual JSON schema might want some adjustment based on feedback. So, consider the --cli-schema option to be a preview option.

Details

Running dotnet --cli-schema will output the entire structure of the CLI. Adding --cli-schema to any command (such as dotnet workload install --cli-schema) will output only the structural information about that command (in this case, dotnet workload install).

Let's look at a simple example for dotnet clean.

Example

When running dotnet clean --cli-schema, you will get this output:

{
  "name": "clean",
  "version": "10.0.100-dev",
  "description": ".NET Clean Command",
  "hidden": false,
  "aliases": [],
  "arguments": {
    "PROJECT | SOLUTION": {
      "description": "The project or solution file to operate on. If a file is not specified, the command will search the current directory for one.",
      "hidden": false,
      "helpName": null,
      "valueType": "System.Collections.Generic.IEnumerable<System.String>",
      "hasDefaultValue": false,
      "defaultValue": null,
      "arity": {
        "minimum": 0,
        "maximum": null
      }
    }
  },
  "options": {
    "--artifacts-path": {
      "description": "The artifacts path. All output from the project, including build, publish, and pack output, will go in subfolders under the specified path.",
      "hidden": false,
      "aliases": [],
      "helpName": "ARTIFACTS_DIR",
      "valueType": "System.String",
      "hasDefaultValue": false,
      "defaultValue": null,
      "arity": {
        "minimum": 1,
        "maximum": 1
      },
      "required": false,
      "recursive": false
    },
    "--configuration": {
      "description": "The configuration to clean for. The default for most projects is 'Debug'.",
      "hidden": false,
      "aliases": [
        "-c"
      ],
      "helpName": "CONFIGURATION",
      "valueType": "System.String",
      "hasDefaultValue": false,
      "defaultValue": null,
      "arity": {
        "minimum": 1,
        "maximum": 1
      },
      "required": false,
      "recursive": false
    },
    "--disable-build-servers": {
      "description": "Force the command to ignore any persistent build servers.",
      "hidden": false,
      "aliases": [],
      "helpName": null,
      "valueType": "System.Boolean",
      "hasDefaultValue": false,
      "defaultValue": null,
      "arity": {
        "minimum": 0,
        "maximum": 0
      },
      "required": false,
      "recursive": false
    },
    "--framework": {
      "description": "The target framework to clean for. The target framework must also be specified in the project file.",
      "hidden": false,
      "aliases": [
        "-f"
      ],
      "helpName": "FRAMEWORK",
      "valueType": "System.String",
      "hasDefaultValue": false,
      "defaultValue": null,
      "arity": {
        "minimum": 1,
        "maximum": 1
      },
      "required": false,
      "recursive": false
    },
    "--interactive": {
      "description": "Allows the command to stop and wait for user input or action (for example to complete authentication).",
      "hidden": false,
      "aliases": [],
      "helpName": null,
      "valueType": "System.Boolean",
      "hasDefaultValue": true,
      "defaultValue": false,
      "arity": {
        "minimum": 0,
        "maximum": 1
      },
      "required": false,
      "recursive": false
    },
    "--nologo": {
      "description": "Do not display the startup banner or the copyright message.",
      "hidden": false,
      "aliases": [],
      "helpName": null,
      "valueType": "System.Boolean",
      "hasDefaultValue": false,
      "defaultValue": null,
      "arity": {
        "minimum": 0,
        "maximum": 0
      },
      "required": false,
      "recursive": false
    },
    "--output": {
      "description": "The directory containing the build artifacts to clean.",
      "hidden": false,
      "aliases": [
        "-o"
      ],
      "helpName": "OUTPUT_DIR",
      "valueType": "System.String",
      "hasDefaultValue": false,
      "defaultValue": null,
      "arity": {
        "minimum": 1,
        "maximum": 1
      },
      "required": false,
      "recursive": false
    },
    "--runtime": {
      "description": null,
      "hidden": false,
      "aliases": [
        "-r"
      ],
      "helpName": "RUNTIME_IDENTIFIER",
      "valueType": "System.String",
      "hasDefaultValue": false,
      "defaultValue": null,
      "arity": {
        "minimum": 1,
        "maximum": 1
      },
      "required": false,
      "recursive": false
    },
    "--verbosity": {
      "description": "Set the MSBuild verbosity level. Allowed values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].",
      "hidden": false,
      "aliases": [
        "-v"
      ],
      "helpName": "LEVEL",
      "valueType": "Microsoft.DotNet.Cli.VerbosityOptions",
      "hasDefaultValue": false,
      "defaultValue": null,
      "arity": {
        "minimum": 1,
        "maximum": 1
      },
      "required": false,
      "recursive": false
    }
  },
  "subcommands": {}
}

The root JSON object contains information about clean. The main groups of information are the "arguments", "options", and "subcommands". Arguments are the non-named parameters to the command (meaning, when running the command, you only provide a value, and not a key-value pair). Options are the named parameters to the command (these are key-value pairs, such as --path "example\path\here"). Subcommands are those commands that this command contains (if applicable). clean contains no subcommands. If it did, these entries would be included with the same information as the root command, except without the additional root information, such as "name" and "version" ("name" is not required for subcommands as the key to each subcommand is the command name).

For more specifics about what each part of information contains, please see the System.CommandLine repository.

Additional Notes

  • I've tweaked the "valueType" information to be different from native CLR type strings, as to use a cleaner, human-readable format.
  • I've adjusted the "arity" information to not be rote S.CL information since maximum is an arbitrary value. Instead, if something is "OrMore", it'll have a null value for maximum instead.
  • I did some additional work to make sure "defaultValue" can output properly into JSON based on the value type of the argument/option.
  • I did not create a JSON schema for this output, but one should be made prior to making this option non-hidden. See: https://json-schema.org/
  • I allowed telemetry to be output when this option is used, which includes the command itself. For example, dotnet workload install --cli-schema will contain a property in the telemetry named command with the value dotnet workload install for this specific telemetry event name (which is dotnet/cli/schema).

…utput the structure of that command in JSON. This still has work to be done to refine the output.
@MiYanni MiYanni requested a review from baronfel May 22, 2025 23:37
MiYanni added 3 commits May 23, 2025 16:32
…ded name for root command. Cleaned up type output. Added default value output. Removed ordering for arguments. Added relaxed JSON encoding.
… in-person discussion. Added version to the root. Shared json serialization options. Created strings for the --cli-schema description.
@MiYanni MiYanni marked this pull request as ready for review June 14, 2025 01:11
@MiYanni MiYanni requested review from a team June 14, 2025 01:11
Copy link
Member

@baronfel baronfel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made a few relatively small changes based on the feedback - once tests are green this is good to go!

baronfel added 3 commits June 14, 2025 10:51
…can easily emit a schema later on.

stylistic changes:
* don't emit null values
* don't emit empty nesting objects/maps
* add an order property to arguments
@baronfel
Copy link
Member

Went kind of ham and switched to using record structures to 'capture' the json output so we can easily emit a schema later on. We talked about this when I was in Redmond @MiYanni if you remember?

stylistic changes:

  • don't emit null values
  • don't emit empty nesting objects/maps
  • add an order property to arguments

@baronfel
Copy link
Member

Alright - the only failing test left is the Docker rate limiting one. Lets merge and get this in p6!

@baronfel baronfel merged commit 73d5633 into dotnet:main Jun 15, 2025
30 checks passed
@MiYanni
Copy link
Member Author

MiYanni commented Jun 17, 2025

@baronfel

stylistic changes:

  • don't emit null values
  • don't emit empty nesting objects/maps
  • add an order property to arguments
  1. I intentionally emitted null values as that represents a certain state. This is something that you can declare in a JSON schema.
  2. I intentionally emitted empty collections/objects as we don't have a published schema and this JSON document is not being sent between a client/server. You want the structure to remain a constant so deserialization by tooling and consistency on understanding by AI would be easier since the data is more patterned if everything is always emitted.
  3. In the future, let's make arguments an array so the order is preserved that way. That's a fairly easy fix.

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

Successfully merging this pull request may close these issues.

Create a way to get a structured representation of a given .NET CLI command
3 participants