This document contains information for contributors to the mittwald CLI. It contains information on how to contribute code to the CLI, as well as some general guidelines for the CLI's development. If you are looking for information on how to use the CLI, please refer to the README.
There is no hard rule for this, but the following guidelines should be followed.
Features that should (usually) be added to the CLI:
- Features that are used by developers in the regular lifecycle of a project (including setup and teardown)
- Features that might commonly be used in a CI/CD pipeline
- Features that otherwise make sense to be scripted or otherwise automated
Features that would (usually) not be added to the CLI:
- Features that by design depend on a graphical interface (avatar upload, ...)
- Features that a typical user would only use once or twice (registration, ...)
- Features that are obviously easier to use through the web interface (e.g. conversation or contract handling)
For a development environment for this CLI, you need:
- Node.JS (>=20.7.0)
- Yarn
- 7zip + NSIS (for Windows builds)
- Docker (for building the Docker image)
When you are using an IDE that supports devcontainers, you can use the provided devcontainer configuration to get a development environment with all required tools.
Every command should have a short description and a long description. The short description should be a single sentence that describes the purpose of the command. The long description should be a more detailed description of the command and explain the purpose, usage and inner workings of the command as necessary.
Also make use of the examples property to provide usage examples for the
command. These examples will be displayed as part of the command's help output.
Examples are useful to demonstrate the usage of flags and arguments, especially
for more complex commands.
List commands should be implemented by inheriting the ListBaseCommand and
implementing the abstract getData and mapData methods. Typically, you will
also want to override the getColumns method.
Some implementation hints regarding the table output:
- The first two columns should be the ID and Short ID columns (the latter only
if the resource has a short ID). Make sure to set the
minWidthproperty for both columns, as these values are not useful to the user if they are truncated. - If a resource has a short ID, and the short ID can be used in place of the
regular ID, the columns should present the short ID as the regular ID; in this
case, the regular ID should be hidden behind the
--extendedflag. - The last column should be the "created at" column, if the resource has a creation date.
- Add other columns as needed. Use the
extendedoption to hide columns that are not relevant for the user in the default output.
You can re-use the most common column definitions, by invoking
super.getColumns, and extracting the columns you need from the result:
const { id, shortId, createdAt } = super.getColumns(row);
return {
id,
shortId,
// ... other columns
createdAt,
};Every list command should support the --output=txt|csv|json|yaml flags to
control the output format. This is automatically supported when inheriting from
ListBaseCommand.
Any kind of command that displays a single resource should inherit from
RenderBaseCommand. This class provides the render method that can be used to
render the resource in a text format. The render method should return a React
node that renders the resource.
Some implementation notes:
- Use the
<SingleResult>component to render the individual attributes of the resource. Add multiple instances of the<SingleResult>component to render multiple sections of data for a single resource. - Use the
<RenderJson>component to render a JSON representation of the resource, if the--outputflag is set tojson. Currently, this needs to be implemented within therendermethod.
Mutating commands (like create, update or any more specialized commands)
should inherit from the ExecRenderBaseCommand, which can be used to visualize
the progress of long-running processes.
The ExecRenderBaseCommand requires you to define a result data type, which
will be printed to stdout when the command completes successfully and JSON
output is required.
Some implementation notes:
- Use the
ProcessRendererclass (to be instantiated with themakeProcessRendererfunction) to render the progress of the process; the class also offers an option to prompt additional/missing input data from the user. - Wrap each long-running operation (API calls, especially when you need to poll the results) into its own separate process step.
Implement delete commands by inheriting from the DeleteBaseCommand. This class
will automatically prompt the user if they want to delete the resource, and will
also handle the case where the user aborts the deletion.
Oftentimes, commands are executed in the context of a certain resource, like an organization, a project or a server. For the most common of these contexts, we provide functions to retrieve the respective context. These functions are:
withProjectIdwithOrganizationIdwithServerIdwithAppInstallationIdwithStackId- possibly more to come
Using these functions typically requires providing an argument or a flag to the respective command that can be used to specify the respective context ID, when it can not be retrieved by any other means:
import { projectFlags } from "./flags";
import { ExtendedBaseCommand } from "./ExtendedBaseCommand";
class MyCommand extends ExtendedBaseCommand<typeof MyCommand> {
static flags = {
...projectFlags,
};
}Use flags and arguments to model inputs. Follow the following guidelines when defining flags and arguments.
- Use flags for optional inputs
- Use arguments for required inputs; commands that affect an existing resource should accept that resource's ID as the first argument.
Both flags and arguments should be named in kebab-case.
When defining flags or arguments, use specialized unit data types when possible. Currently, the following specialized unit data types are available for defining flags:
ByteQuantity.flagcan be used to define flags that accept byte quantities (e.g.--size=1Gor--size=1024M)Duration.relativeFlagandDuration.absoluteFlagcan be used to define flags that accept durations (e.g.--timeout=1hor--timeout=3600s). The difference between the two types is thatrelativeFlagwill map to aDurationtype, whereasabsoluteFlagwill map to aDatetype (with the specified duration added to the current date).
To run the CLI with your changes, you have multiple options:
- Run the dev runnable in your console:
This is slow, since it complies TypeScript code on the fly with
> bin/dev.js # example > bin/dev.js context get
tsx. - Run the prebuilt js runnable in your console:
Faster than
> bin/run.js # example > bin/run.js context get
devbutyarn compilemust be called manually first and after each change.
- If feasible, create unit tests for your changes. Run them with
yarn test:unit. - Make sure the code is formatted and fulfills linter rules. Run
yarn formatto autoformat code andyarn lintto run linter. - Generate docs and readme changes using
yarn generate:readme. - Create a pull request. Explain your changes; ideally, you should link to a ticket.