Digging in the project for the first time might make it difficult to understand where to start. Hopefully, this guide will provide you with a better understanding of the project structure.
There are some ways to setup the development environment. You can run directly from your favorite IDE or overwrite Ritchie's binary on your machine. The most popular IDEs are GoLand from JetBrains and VSCode from Windows.
-
On the top menu go to
Run > Edit Configurations
. Click on the+
button on the top left and select to create a newGo Build
. -
On Files, select the
cmd/main.go
file as the entry point of your program. -
As for Working Directory, use the project root
-
Under Program Arguments, write the command you want to run without the
rit
. For instance, if you want to debugrit create formula
, just addcreate formula
on this field.
If you want to basically use your local code as the running Ritchie distribution on your machine, just run this simple command from your project root directory in the terminal:
- Linux:
make build-linux && sudo mv ./dist/linux/rit /usr/local/bin
- Mac:
make build-mac && sudo mv ./dist/darwin/rit /usr/local/bin
- Windows:
make build-windows && runas \user:administrator move .\dist\windows\rit.exe "\Program Files (x86)\rit"
There are two main libraries that are most used throughout the project. These are the ones that aid us directly in building a solid cli, and probably the ones you will have most contact with.
Cobra is a powerful cli building tool. Many modern projects use it to build their clis. We can map out all commands and subcommands in Ritchie and Cobra will take care of executing them with the right methods and flags, provide helper descriptions for users, and even more features. Check its github repo to learn more about it.
One of the key features of Ritchie is that it lets you run formulas in an interactive manner. To help us achieve a better user experience, we use survey. Survey is a library of cli prompts in various formats. It supports text inputs, secret inputs, list selection, among other things.
For this section, we discuss the main folders in the project and their purpose.
This is where the main
file resides. Ritchie comes natively with a set of core commands to manipulate the cli.
These commands range from managing workspaces and credentials, to building and creating formulas. We make heavy
use of dependency injection to make these commands testable. Therefore, most of the components are initialized here
and then added to Cobra
. If you have any new command to add, you will be definitely editing this file. Keep in mind
the organisation of the commands!
Currently, we have two main ways of running formulas. Formulas can be run interactively or via stdin. This folder contains the files to run formulas on the different supported OSes and with different input formats.
Contains code and instructions to package Ritchie for different supported OS distributions. Here we have scripts for the Windows installer, rpm and debian distributions, and other installation scripts.
This module is the core of the project, any command or functionality is generally developed here and added to Cobra
via main
. Some of the features developed are listed below:
- api: contains main constants such as the user home directory and core commands list.
- autocomplete: contains inline scripts and logic for the
rit completion
commands. - cmd: contains most of core commands implementation. You can find commands such as listing, creating, and deleting resources here. For simple implementations they are enough, but they might call dedicated modules to perform certain actions.
- credential: contains logic related to credential manipulation, such as the files they are saved and how to manage them.
- env: manages user environments. Helper to resolve credential from input runners, using different sets of credentials (i.e.: development, staging, production)
- formula: contains all formula manipulation implementations, such as building, resolving, creating, and running formulas
- git: module to add, remove, or manage other formula repos.
- http: header definitions
- metric: sends collected anonymous metrics via http requests.
- prompt: Ritchie's adaptations on the
survey
module for user input. - rtutorial: basic implementation of tutorial texts. Tutorial are helper texts that can be added to each command to provide the user with more context on that action.
- stdin: JSON stdin parser
- upgrade: manages and performs upgrades on Ritchie
- version: manages and resolves Ritchie's versioning
Contains multiple files used for unit and functional tests.
Ritchie relies heavily on dependency injection to test its code. If you are creating a new command, you should
pass on any required dependencies from the main.go
initialization file. Usually you pass on inputs to prevent
the test from blocking the execution flow while awaiting for an user input. For instance, suppose you have a list
that offers the options a, b, c
. Then, you simply pass a mock that returns a
straight away in place of the listing
struct to perform your test.
When testing, think about the intended behavior of your command. Does it create or modify a file? Does it manipulate
a directory? You can make use of the os.TempDir()
command to setup your sandbox file system and manipulate files
and folders there. Let's suppose your implementation edits the credential config file. You do not want to meddle
with your own config file every time you run a test. So simply create one in a temp directory and pass its path
to the command *as if it was ritchie's home directory.
Do not forget to remove your temp directory to not flood your file system with trash on each run! You can use defer
to ensure a piece of code will always be executed after the method returns regardless of where the execution ended.
defer os.RemoveAll(filePath)
Test files are suffixed with _test
. Test functions have the format TestCamelCase
which will be printed when
you execute them. If your test has multiple outcome branches, you can create a struct array to iterate over such cases.
The struct usually has the format:
tests := []struct {
name string // Name of the test to display
fields fields // Contains all necessary struct mocks or values to perform the test
output bool // Expected result to compare to
}
A typical test has the following structure
// Mocks initialization here
var tests = []struct {
name string
fields fields
output string
}{
{
name: "Some name",
fields: fields{ ... },
output: "Some output",
},
...,
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Your execution code here that compares with the output
})
}
Note: we are currently reviewing how our tests are written, we are trying to make a more assertive test framework on Ritchie cli, such as simulating the user input via stdin instead of mocking it, so these practices might change.
Ritchie CLI uses Github Actions to perform functional tests in parallel on ubuntu
, macOs
and windows
.
To do so, the team created an action to check command lines outputs: test-cli-commands-action
The objective is to check if the command output is the same as expected according to the scenarios specified (success or failure).
Those workflows will trigger on any pull request or push event updating files related to the commands being tested.
This is the way the action works:
There are 3 ways to check a command line output using this action:
- Comparing the command line output with an assert file.
- Comparing a specific line of the command line output with an assert file.
- Checking if a specific expression is present in the command line output.
When possible, we tried as much as possible to compare command line outputs using assert files, but as the command line outputs files are created using different OS, or return specific datas depending on the date, we couldn't test all scenarios with this option alone, that's the reason why other features as been added to the action as well.
Note: TXT files used for ASSERT are located on the /testdata/gha_workflows
directory.
When testing new commands or scenarios, we suggest to use the cat
commands to show each file you will compare on the workflow window on Github. Example:
rit list credential > check1.txt
cat check1.txt
cat testdata/gha_workflows/credential_workflow/assert1.txt
diff check1.txt testdata/gha_workflows/credential_workflow/assert1.txt
Then, convert it to the action nomenclature:
- uses: GuillaumeFalourd/test-cli-commands-action@v1
with:
command_line: <command line to test>
assert_file_path: path/to/assert.txt
specific_line: 3
contains: <string expression>
expected_result: <PASSED or FAILED> (if you want to test Success or Error)
Check the action documentation about those inputs to understand exactly how to use them.
To test your workflows, create a branch on your fork and test your workflows there using the workflow_dispatch
trigger.
- All translation files are inside the
/resources/i18n
folder. - Translation files follow a convention, must start with your locale, and have the extension
.toml
. For example:en.toml
- Any phrase or word that does not exist in
en.toml
must be added to it to avoid mistakes. - The convention for translation IDs is to start with the name of the specific package or command followed by a clear identifier for your word or phrase. For example:
[init.welcome]
[init.hello.world]
other = "Hello World!"
[init.hello.world]
other = "Olá Mundo!"
[init.hello.world]
other = "Hallo Welt!"
Note: you must run the
make generate-translation
command to update thetranslations.go
file whenever you create or update a translation.
You will use the i18n
package located inside internal/pkg/i18n
. See an example below:
import "github.com/ZupIT/ritchie-cli/internal/pkg/i18n"
var helloWorldMsg = i18n.T("hello.world") // [hello.world] is the message ID.
i18n.Langs
.
Example:
package i18n
var Langs = map[string]string{
"English": "en",
"Portuguese": "pt_BR",
"German": "ge_GE", // Must be the same name as the translation file
}
We have a packaging
directory that contains a mac-pkg
subdirectory with a script (inspired from KosalaHerath/macos-installer-builder) to generate a pkg install from rit darwin binary.
Here is the step by step to follow to generate it:
- Create an
Application
folder onpackaging/mac-pkg
folder. - At the repository root, run
make build-mac && sudo mv ./dist/darwin/rit ./packaging/mac-pkg/Application
. - Access the mac-pkg folder:
cd packaging/mac-pkg
- Run
bash build-macos-x64.sh rit <version>
- Your package file should be available at
packaging/mac-pkg/target/pkg
Note: You need to have a developer certificate
installed on keychain
on the macOS computer that will run the command to sign the pkg. In that case, you'll need to run bash build-macos-x64.sh rit <version> "<developer_installer_id>"
, and your package file should be available at packaging/mac-pkg/target/pkg-signed
.