Spis treści

Czym jest CLI?


CLI (ang. Command Line Interface) to interfejs, dzięki któremu możemy komunikować się z aplikacją za pomocą terminalu. Większość systemów operacyjnych ma taki interfejs wbudowany, dostępny dla użytkownika. Jest to nic innego jak zbiór funkcji, które użytkownik może wykonać z wykorzystaniem wiersza poleceń zamiast rozbudowanego interfejsu graficznego.

Opis Windows Linux
Wyświetlanie listy plików w aktywnym katalogu dir ls
Kopiowanie pliku copy cp
Przenoszenie lub zmiana nazwy pliku ren mv
Zmiana uprawnień do pliku attrib chmod
Zakończenie aktywnego procesu taskkill /IM code.exe kill -9

OK, ale po co mi to?

Trafne pytanie. Jak pewnie widzisz, wszystkie wypisane wyżej komendy można z powodzeniem wykonać z użyciem interfejsu graficznego. Niemniej jednak mogą zdarzyć się sytuacje, w których wykonanie polecania będzie po prostu szybsze.

Kilka praktycznych przykładów:

Są też przypadki w których w ogóle nie mamy możliwości z korzystania z interfejsu graficznego np. podczas połączenia SSH (ang. secure shell) z serwerem.

Wiele serwisów udostępnia swoje CLI, aby w prosty i szybki sposób użytkownik mógł zarządzać swoimi usługami.

Popularne CLI


Github - gh

  • Tworzenie release - gh release create <tag>
  • Wyświetlanie issues - gh issue list
  • Przełączanie się na Pull Request - gh pr checkout <id>

WordPress - wp

  • Wyświetlanie informacji o zarejestrowanych typach postów - wp post-type list
  • Usuwanie użytkownika - wp user delete <id>
  • Regenerowanie miniatur - wp media regenerate --yes

Vercel - vercel

  • Logowanie vercel login
  • Deploy na środowisko testowe - vercel dev
  • Dodanie zmiennych środowiskowych vercel env add [name] [environment]

Nasze CLI


Powodów, dla których warto stworzyć własne CLI, jest niewątpliwie sporo. Głównym czynnikiem jest z pewnością optymalizacja czasu pracy. Dobrze zrobione narzędzie może być wykorzystywane przez cały zespół. Dzięki czemu możemy wyeliminować powielane błędy towarzyszące przy ręcznym wykonywaniu komend. Tworzenie takiego zestawu komend jest znacznie szybsze niż tworzenie aplikacji z interfejsem graficznym.

Jedno z naszych CLI służy do zarządzania starterem WordPress. Umożliwia nam ono m.in. tworzenie komponentów z gotową strukturą, instalowanie zależności oraz konfigurowanie projektu.

tworzenie cli

Jak zacząć?


Do stworzenia własnego CLI wykorzystamy popularny framework oclif, który działa w środowisku node.js. Zawiera zestaw gotowych narzędzi, dzięki którym tworzenie naszego interfejsu będzie bajecznie proste!

Wymagania

Inicjalizacja

npx oclif generate <name> - uruchamiamy tę komendę i postępujemy zgodnie z instrukcjami. Przypatrz się dokładnie, w jaki sposób oclif wyświetla pola do wpisania tekstu oraz wyboru wartości. Analogicznie będą wyglądały opcje w naszym CLI.

npx oclif generate name

Po wypełnieniu wszystkich wymaganych pól naszym oczom powinna ukazać się wygenerowana struktura katalogów.

cli wygenerowana struktura katalogów

Uruchomienie testowego polecenia

Po inicjalizacji projektu możemy sprawdzić, czy pobrane pliki działają poprawnie, wpisując w terminalu ./bin/dev hello world. Powinieneś ujrzeć wiadomość zwrotną.

Struktura katalogów

  • /bin - Folder z plikami wykonywalnymi
  • /src/commands - Folder z komendami. Każdy podfolder oznacza nową komendę
  • /test - Folder z testami

Komendy


Każda komenda to nic innego jak klasa rozszerzająca klasę abstrakcyjną Command z biblioteki @oclif/core. Zawarta w niej asynchroniczna funkcja run() jest wywoływana za każdym razem, gdy z poziomu CLI wywołujemy komendę dotyczącą danej klasy.

  • npx oclif generate command <name> - generowanie struktury komendy oraz testu. Struktura katalogów komend sprawia, że będą one wykonywane w podanej kolejności np. rc component generate test

Argumenty

Do dyspozycji mamy również argumenty. Jest to nic innego jak statyczna tablica z obiektami w klasie komendy.

Przykład: rc component createarg1 arg2

static args = [
    {
    name: 'file',               // name of arg to show in help and reference with args[name]
    required: false,            // make the arg required with `required: true`
    description: 'output file', // help description
    hidden: true,               // hide this arg from help
    parse: input => 'output',   // instead of the user input, return a different value
    default: 'world',           // default value if no arg input
    options: ['a', 'b'],        // only allow input to be from a discrete set
    }
]

Flagi

Są to dodatkowe opcje, które możemy wykorzystać podczas wywoływania komend. Podobnie jak w przypadku argumentów flagi, również definiuje się jako metodę statyczną w klasie komendy.

Przykład: rc component create--force --dev=test

static flags = {
  name: Flags.string({
    char: 'n',                    // shorter flag version
    description: 'name to print', // help description for flag
    hidden: false,                // hide from help
    multiple: false,              // allow setting this flag multiple times
    env: 'MY_NAME',               // default to value of environment variable
    options: ['a', 'b'],          // only allow the value to be from a discrete set
    parse: input => 'output',     // instead of the user input, return a different value
    default: 'world',             // default value if flag not passed (can be a function that returns a string or undefined)
    required: false,              // make flag required (this is not common and you should probably use an argument instead)
    dependsOn: ['extra-flag'],    // this flag requires another flag
    exclusive: ['extra-flag'],    // this flag cannot be specified alongside this other flag
  }),

  // flag with no value (-f, --force)
  force: Flags.boolean({
    char: 'f',
    default: true,                // default value if flag not passed (can be a function that returns a boolean)
    // boolean flags may be reversed with `--no-` (in this case: `--no-force`).
    // The flag will be set to false if reversed. This functionality
    // is disabled by default, to enable it:
    // allowNo: true
  }),
}

Interakcja

Do interakcji z użytkownikiem możemy wykorzystać zestaw gotowy funkcji prompt. Dzięki nim możemy wyświetlić pola do wpisania tekstu, a nawet listy z gotowymi opcjami z pomocą biblioteki inquirer. Takie dane są przechowywane w pamięci, dzięki czemu możemy z nich korzystać podobnie jak ze zmiennych.

prompt demo

(źródło: oclif.io/docs/prompting)

Obsługa błędów

Każdy nieobsłużony błąd przerywa działanie CLI, wyświetlając stosowny komunikat. W dokumentacji znajdziesz całych rozdział poświęcony temu zagadnieniu.

Testy

Do pisania testów najlepiej jest skorzystać @oclif/test. Testy bazują na frameworku Mocha. Więcej informacji znajdziesz w dokumentacji.

Wskazówki

  • ./bin/dev <command_ name> - uruchomienie komendy w trybie developerskim.
  • Pamiętaj, że oclif działa w środowisku Node.js, dzięki czemu możesz korzystać z jego wszystkich udogodnień, takich jak wykonywanie zapytań ajax lub poruszanie się po katalogach systemu.
  • Do wykonywania poleceń systemowych możemy skorzystać z biblioteki shelljs. Zapewnia ona wsparcie dla Windows, Linux oraz OS X.

W celach demonstracyjnych napisałem proste CLI do generowania komponentu w aplikacji React.

Deploy


Gotowy kod możemy udostępnić w serwisie npm, wykonując polecenie npm publish --access public. Od teraz nasz kod jest dostępny jako publiczna paczka, dzięki czemu każdy (oczywiście z zainstalowanym Node.js) może zainstalować CLI na swoim komputerze. Do tego celu wystarczy uruchomić polecenie npm install -g @sebastiansiejek/rc. Po instalacji możemy uruchomić nasze CLI wywołując polecenie rc.

npm package: npmjs.com/package/@sebastiansiejek/rc

Czy warto korzystać z CLI?


Korzystając z CLI, możemy w znaczny sposób ułatwić, przyspieszyć, a może nawet uprzyjemnić naszą pracę. Jeśli przeklikiwanie się przez złożoną strukturę aplikacji zajmuje zbyt dużo czasu lub widzisz, że niektóre funkcje można połączyć i wykonywać jednym uniwersalnym poleceniem, być może warto zastanowić się nad stworzeniem własnego CLI. Dzięki prostocie oraz gotowym funkcjom, które dostarcza nam oclif, tworzenie takiego spersonalizowanego narzędzia jest proste i przyjemne. Zachęcam do zapoznania się z dokumentacją frameworka.