From 2055fcd2dfc5e0d05c716fd85109240b24665d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Gr=C4=85cikowski?= <01180781@pw.edu.pl> Date: Wed, 21 Jan 2026 13:21:40 +0100 Subject: [PATCH] add merge command in cli and update readme --- README.md | 189 +++++++++++++++++- cli/src/commands.rs | 3 + cli/src/commands/merge.rs | 130 +++++++++++++ docs/plugins/README.md | 393 -------------------------------------- 4 files changed, 319 insertions(+), 396 deletions(-) create mode 100644 cli/src/commands/merge.rs delete mode 100644 docs/plugins/README.md diff --git a/README.md b/README.md index fbe6f5a9..0565391c 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,20 @@ Promotorem pracy był **[mgr inż. Tomasz Herman](https://github.com/tomasz-herm - [Architektura](#architektura) - [Struktura projektu](#struktura-projektu) - [Model rozproszony i architektura repozytoriów](#model-rozproszony-i-architektura-repozytoriów) - - [Warstwa sieciowa i protokoły synchronizacji](#warstwa-sieciowa-i-protokoły-synchronizacji) + - [Warstwa sieciowa i protokoły synchronizacji](#warstwa-sieciowa-i-protokoly-synchronizacji) +- [Skrypty użytkownika](#skrypty-uzytkownika) + - [Założenia systemu skryptów użytkownika](#zalozenia-systemu-skryptow-uzytkownika) + - [Koncepcja niezależności językowej](#koncepcja-niezaleznosci-jezykowej) + - [Fizyczna organizacja i konfiguracja skyptów użytkownika](#fizyczna-organizacja-i-konfiguracja-skyptow-uzytkownika) + - [Struktura obiektów kontekstu](#struktura-obiektow-kontekstu) + - [Przykład implementacji](#przyklad-implementacji) - [Instalacja](#instalacja) - - [Wymagania wstępne i środowisko budowania](#wymagania-wstępne-i-środowisko-budowania) + - [Wymagania wstępne i środowisko budowania](#wymagania-wstepne-i-srodowisko-budowania) - [Instalacja klienta CLI](#instalacja-klienta-cli) - [Instalacja klienta GUI](#instalacja-klienta-gui) - [Instalacja i konfiguracja serwera SSH](#instalacja-i-konfiguracja-serwera-ssh) - [Weryfikacja instalacji](#weryfikacja-instalacji) -- [Instrukcja użytkownika](#instrukcja-użytkownika) +- [Instrukcja użytkownika](#instrukcja-uzytkownika) - [Praca z interfejsem CLI](#praca-z-interfejsem-cli) - [Praca z interfejsem GUI](#praca-z-interfejsem-gui) - [Praca z serwerem SSH](#praca-z-serwerem-ssh) @@ -398,6 +404,183 @@ Diagram sekwencji dla protokołu `meva-receive-pack`:

+## Skrypty użytkownika + +System kontroli wersji MEVA został zaprojektowany jako rozwiązanie otwarte na rozszerzenia. Oprócz wbudowanego zestawu poleceń, udostępnia on mechanizm skryptów użytkownika (nazywanych również pluginami), który pozwala na automatyzację zadań, walidację danych oraz integrację zewnętrznych narzędzi bez konieczności ingerencji w kod źródłowy samej aplikacji. + +### Założenia systemu skryptów użytkownika + +Projektując moduł skryptów użytkownika, przyjęto szereg założeń definiujących jego zachowanie, zakres oraz sposób interakcji z otoczeniem: + +- **Model zdarzeń**: System obsługuje dwa rodzaje zdarzeń: + - `pre-execute`: Występujące przed wykonaniem właściwej operacji (np. `commit`, `push`). Służy do walidacji oraz modyfikacji danych wejściowych. Błąd zwrócony przez plugin w tej fazie skutkuje natychmiastowym przerwaniem całej operacji. + - `post-execute`: Występujące po pomyślnym wykonaniu operacji. Służy do raportowania i analizowania wyników polecenia. Błąd w tej fazie nie wpływa na wynik operacji wykonanej już na repozytorium. + +- **Zakresy**: Pluginy mogą być rejestrowane na dwóch poziomach widoczności: + - **Lokalny**: Ogranicza działanie skryptu do pojedynczego repozytorium. Konfiguracja i skrypty przechowywane są wewnątrz katalogu `.meva/plugins/`. + - **Globalny**: Rozszerza widoczność na wszystkie repozytoria danego użytkownika w systemie. Konfiguracja i skrypty przechowywane są w katalogu domowym użytkownika (`~/.meva/plugins/`). + +- **Niezależna komunikacja**: Interfejs wymiany danych między systemem a pluginem jest zrealizowany poprzez system plików. W momencie wywołania, skrypt otrzymuje ścieżkę do tymczasowego pliku JSON (tzw. pliku kontekstu), co uniezależnia mechanizm od języka programowania, w którym napisano rozszerzenie. + +- **Synchroniczne wykonanie**: Pluginy są uruchamiane synchronicznie, jeden po drugim. Kolejność ich wykonywania wynika z priorytetu (`order`) nadanego podczas rejestracji. + +- **Kontrola czasu wykonania**: Istnieje możliwość zdefiniowania maksymalnego czasu wykonania osobno dla każdego zarejestrowanego skryptu. Jeżeli wykonanie logiki zapisanej w pluginie potrwa dłużej niż określony limit, system automatycznie przerywa wykonanie, informując o przekroczeniu czasu. + +- **Przechwytywanie wejścia/wyjścia**: System w pełni zarządza standardowymi strumieniami (`stdin`, `stdout`, `stderr`) każdego skryptu. Strumienie wyjściowe są przechwytywane w czasie rzeczywistym, dzięki czemu komunikaty wypisywane na standardowe wyjście (`stdout`) i standardowe wyjście błędów (`stderr`) są jednocześnie prezentowane użytkownikowi w konsoli oraz zapisywane w logach wywołania. Skrypty mogą pobierać dane od użytkownika poprzez standardowe wejście (`stdin`). + +- **Konfiguracja**: Działanie całego systemu rozszerzeń jest kontrolowane przez wpisy w głównych plikach konfiguracyjnych (sekcja `[plugins]`), co pozwala na ich szybkie włączenie lub wyłączenie bez konieczności odinstalowywania skryptów czy rekompilacji kodu. + +### Koncepcja niezależności językowej + +Istotną cechą systemu rozszerzeń jest brak wymogu tworzenia pluginów w konkretnym języku programowania. Zamiast dostarczać wewnętrzne API lub biblioteki dynamiczne (`.dll`, ang. _dynamic link libraries_), system **MEVA** wykorzystuje mechanizm procesów potomnych oraz komunikację opartą na plikach. Procesy potomne komunikują się poprzez ustandaryzowany plik kontekstu operacji w formacie [JSON](https://json.org/json-en.html). + +Plugin został zdefiniowany jako dowolny plik wykonywalny lub skrypt interpretowany (na przykład w języku `Python`, `JavaScript`, `PowerShell` czy `Bash`). + +Proces wymiany danych wygląda następująco: + +1. System MEVA generuje plik tymczasowy w formacie JSON, zawierający metadane polecenia (kontekst wykonywanej operacji). +2. System uruchamia skrypt użytkownika jako osobny proces, przekazując ścieżkę do pliku kontekstu jako pierwszy argument wywołania. +3. Skrypt odczytuje dane, wykonuje swoją logikę i (opcjonalnie) modyfikuje plik kontekstu. Może również zapisać w nim informację o błędzie oraz zwrócić niezerowy kod wyjścia, przerywając wykonanie kolejnych skryptów lub samego polecenia. + +Dzięki takiemu podejściu użytkownik ma pełną swobodę w doborze technologii do tworzenia rozszerzeń, o ile w danym środowisku systemowym dostępny jest odpowiedni interpreter. + +### Fizyczna organizacja i konfiguracja skyptów użytkownika + +System pluginów posiada własną, ustrukturyzowaną hierarchię plików, która umożliwia logiczną organizację skryptów i ich metadanych. Zdefiniowano dwa poziomy zasięgu pluginów: globalny (dla użytkownika systemu operacyjnego) oraz lokalny (dla konkretnego repozytorium). + +``` +.meva/plugins/ +├── .invocations/ +│ ├── commit/ +│ │ ├── 20260112-202222/ +│ │ │ ├── invocation.log +│ │ │ ├── pre-execute-context.json +│ │ │ ├── post-execute-context.json +│ │ │ ├── stdout.log +│ │ │ └── stderr.log +│ │ └── ... +│ └── ... +├── commit/ +│ ├── plugins.json +│ ├── validate_message.py +│ └── ... +├── config/ +│ ├── set/ +│ │ ├── plugins.json +│ │ └── ... +│ └── unset/ +│ ├── plugins.json +│ └── ... +└── ... +``` + +### Struktura obiektów kontekstu + +Komunikacja między systemem MEVA a skryptami użytkownika odbywa się poprzez wymianę sformalizowanych obiektów danych w formacie JSON. + +Każdy plik kontekstu operacji, do którego ścieżka jest przekazywana jako pierwszy argument pozycyjny podczas uruchamiania skryptu, zawiera cztery główne sekcje: metadane (`context`), dane wejściowe (`pre-payload`), dane wyjściowe (`post-payload`) oraz kanał błędów (`error`). + +```json +{ + "context": { + "command": "commit", + "event": "pre-execute", + "timestamp": "2026-01-12T12:34:56Z", + "working_dir": "/home/john/company_project" + }, + "pre-payload": { + "message": "Implement user authentication.", + "author": { + "name": "John Doe", + "email": "john.doe@example.com" + }, + "amend": false + }, + "post-payload": { + "commit_hash": "a1b2c3d4e5f6...", + "changes": [ + { + "added": { + "new_path": "src/auth.rs", + "insertions": 45 + } + } + ] + }, + "error": null +} +``` + +Rola poszczególnych sekcji obiektu JSON jest następująca: + +1. `context`: Sekcja zawierająca podstawowe informacje o kontekście wywołania skryptu użytkownika. Znajdują się tu informacje o typie polecenia (`command`), rodzaju zdarzenia (`event`), czasie wywołania (`timestamp`) oraz katalogu roboczym (`working_dir`). +2. `pre-payload`: Sekcja zawierająca dane wejściowe specyficzne dla danego polecenia. Są one dostępne zarówno w fazie `pre-execute`, jak i `post-execute`. +3. `post-payload`: Sekcja przeznaczona na wynik działania operacji. W fazie `pre-execute` wartość ta zawsze wynosi `null`. W fazie `post-execute` (jak na rysunku przedstawiającym kontekst polecenia `commit`) zawiera ona informacje dotyczące rezultatu polecenia. +4. `error`: Opcjonalna sekcja, która w przypadku poprawnego wykonania skryptu użytkownika przyjmuje wartość `null`. Plugin może ją zmodyfikować w celu zgłoszenia błędu wykonania. + +W przypadku polecenia `commit` informacje zawarte w `pre-payload` obejmują między innymi treść wiadomości, dane autora oraz flagi sterujące (np. `amend`). Sekcja `post-payload` zawiera natomiast między innymi skrót zatwierdzenia, a także listę plików objętych zatwierdzeniem wraz z metadanymi (np. liczbę zmodyfikowanych linii). + +#### Struktura obiektów błędu + +Skrypt użytkownika ma możliwość przerwania łańcucha wywołań następujących po nim pluginów, a w fazie `pre-execute` — dodatkowo zablokowania wykonania głównego polecenia (np. uniemożliwienia utworzenia zatwierdzenia, którego format wiadomości nie spełnia określonych wymogów). + +Sygnałem do przerwania operacji jest zakończenie procesu pluginu z kodem wyjścia (ang. _exit code_) różnym od zera. W takiej sytuacji system **MEVA** analizuje pole `error` w pliku kontekstu, aby wyświetlić użytkownikowi przyczynę blokady. + +```json +{ + "code": "VALIDATION_ERROR", + "message": "Commit message must reference an Azure DevOps work item (e.g. AB#1234)", + "details": "Regex pattern '^AB#\\d+' not matched" +} +``` + +Obiekt ten składa się z trzech pól: + +1. `code`: Stały identyfikator typu błędu (np. `VALIDATION_ERROR`). +2. `message`: Komunikat przeznaczony bezpośrednio dla użytkownika końcowego, wyjaśniający, dlaczego operacja została przerwana. +3. `details`: Opcjonalne pole zawierające szczegóły techniczne, pomocne w diagnostyce. + +### Przykład implementacji + +W celu zaprezentowania praktycznego zastosowania omówionych mechanizmów, przedstawiona zostanie przykładowa implementacja pluginu walidacyjnego. Scenariusz ten zakłada integrację systemu MEVA z platformą Azure DevOps. Jedną z cech tej platformy jest identyfikowanie elementów roboczych (ang. _work items_) poprzez unikalny identyfikator (liczbę). + +Wymaganiem biznesowym może być oznaczanie każdego zatwierdzenia identyfikatorem odpowiadającego mu elementu roboczego poprzez umieszczenie tego identyfikatora na początku wiadomości zatwierdzenia. + +Poniżej przedstawiono kompletny kod skryptu napisanego w języku Python. Realizuje on logikę walidacji w fazie `pre-execute`, wykorzystując mechanizm pliku kontekstu do komunikacji z systemem MEVA. + +```python +#!/usr/bin/env python3 +import sys, json, re + +filename = sys.argv[1] +with open(filename, "r", encoding="utf-8") as f: + data = json.load(f) + +msg = data.get("pre-payload", {}).get("message", "") + +pattern = r"^AB#[0-9]+" + +if not re.match(pattern, msg): + data["error"] = { + "code": "VALIDATION_ERROR", + "message": "Commit message must reference an Azure DevOps work item (e.g. AB#1234)", + "details": f"Regex pattern '{pattern}' not matched" + } + with open(filename, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2) + sys.exit(1) + +data["error"] = None +with open(filename, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2) + +sys.exit(0) +``` + +Wszystkie pliki związane z rozszerzeniami przechowywane są w katalogu `plugins/`. Dla zasięgu lokalnego katalog ten znajduje się wewnątrz folderu `.meva/`, natomiast dla zasięgu globalnego jego lokalizacja to katalog domowy użytkownika (ścieżka ``~/.meva/plugins/`). + +Pluginy są grupowane w katalogach odpowiadających nazwom poleceń, na które mają one reagować (np. `commit/`, `config/set/`). Taka struktura umożliwa szybkie zlokalizowanie wszystkich skryptów zarejestrowanych na wywołanie konkretnego polecenia. + ## Instalacja Zarówno narzędzie wiersza poleceń (CLI), jak i interfejs graficzny (GUI) są dystrybuowane jako diff --git a/cli/src/commands.rs b/cli/src/commands.rs index 9f22bdba..d67d5bb8 100644 --- a/cli/src/commands.rs +++ b/cli/src/commands.rs @@ -11,6 +11,7 @@ pub mod init; pub mod log; pub mod ls_files; pub mod ls_tree; +pub mod merge; pub mod meva_command; pub mod plugins; pub mod pull; @@ -33,6 +34,7 @@ pub use init::InitCommand; pub use log::LogCommand; pub use ls_files::LsFilesCommand; pub use ls_tree::LsTreeCommand; +pub use merge::MergeCommand; pub use plugins::PluginsCommand; pub use pull::PullCommand; pub use push::PushCommand; @@ -91,5 +93,6 @@ pub fn collect_commands() -> CommandsCollection { Box::new(PushCommand), Box::new(PullCommand), Box::new(CheckoutCommand), + Box::new(MergeCommand), ] } diff --git a/cli/src/commands/merge.rs b/cli/src/commands/merge.rs new file mode 100644 index 00000000..0b989618 --- /dev/null +++ b/cli/src/commands/merge.rs @@ -0,0 +1,130 @@ +use async_trait::async_trait; +use clap::{Arg, ArgAction, ArgMatches, Command}; +use miette::Result; + +use engine::engine_container::MevaContainer; + +use crate::{commands::MevaCommand, extensions::WithVerbose}; + +/// Implements the `merge` command for Meva DVCS. +#[derive(Default)] +pub struct MergeCommand; + +impl MergeCommand { + /// The branch to merge into the current branch + const ARG_BRANCH: &'static str = "branch"; + + /// Merge changes but do not create a merge commit (squash) + const ARG_SQUASH: &'static str = "squash"; + + /// Abort the current merge process and restore the previous state + const ARG_ABORT: &'static str = "abort"; + + /// Use the given message as the merge commit message + const ARG_MESSAGE: &'static str = "message"; + + /// Allow fast-forward merge if possible + const ARG_FAST_FORWARD: &'static str = "ff"; + + /// Create a merge commit even when fast-forward is possible + const ARG_NO_FAST_FORWARD: &'static str = "no-ff"; +} + +#[async_trait] +impl MevaCommand for MergeCommand { + type Container = MevaContainer; + + fn name(&self) -> &'static str { + "merge" + } + + fn about(&self) -> &'static str { + "Join development histories together" + } + + fn version(&self) -> &'static str { + "1.0.0" + } + + /// Builds the CLI argument parser for the `merge` command. + fn build_command(&self) -> Command { + self.build_base_command() + .with_verbose_arg("Enable verbose output during the merge process") + .arg( + Arg::new(Self::ARG_BRANCH) + .help("The branch to merge into the current branch") + .value_name("BRANCH") + .required_unless_present(Self::ARG_ABORT), + ) + .arg( + Arg::new(Self::ARG_ABORT) + .long(Self::ARG_ABORT) + .short('a') + .help("Abort the current merge process and restore the previous state") + .action(ArgAction::SetTrue) + .conflicts_with_all([ + Self::ARG_BRANCH, + Self::ARG_SQUASH, + Self::ARG_MESSAGE, + Self::ARG_FAST_FORWARD, + Self::ARG_NO_FAST_FORWARD, + ]), + ) + .arg( + Arg::new(Self::ARG_SQUASH) + .long(Self::ARG_SQUASH) + .short('s') + .help("Merge changes but do not create a merge commit (squash)") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(Self::ARG_FAST_FORWARD) + .long(Self::ARG_FAST_FORWARD) + .help("Allow fast-forward merge if possible") + .action(ArgAction::SetTrue) + .conflicts_with(Self::ARG_NO_FAST_FORWARD), + ) + .arg( + Arg::new(Self::ARG_NO_FAST_FORWARD) + .long(Self::ARG_NO_FAST_FORWARD) + .help("Create a merge commit even when fast-forward is possible") + .action(ArgAction::SetTrue) + .conflicts_with(Self::ARG_FAST_FORWARD), + ) + .arg( + Arg::new(Self::ARG_MESSAGE) + .long(Self::ARG_MESSAGE) + .short('m') + .value_name("MESSAGE") + .help("Use the given message as the merge commit message") + .requires(Self::ARG_BRANCH), + ) + } + + /// Executes the `merge` command. + /// + /// # Arguments + /// * `matches`: Parsed command-line arguments. + /// * `container`: Dependency injection container. + /// + /// # Returns + /// * `Result<()>`: Success or error during execution. + async fn execute(&self, _matches: &ArgMatches, _container: &Self::Container) -> Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use rstest::rstest; + + #[rstest] + fn test_command_name_about_version() { + let cmd = MergeCommand; + assert_eq!(cmd.name(), "merge"); + assert_eq!(cmd.about(), "Join development histories together"); + assert_eq!(cmd.version(), "1.0.0"); + } +} diff --git a/docs/plugins/README.md b/docs/plugins/README.md deleted file mode 100644 index ea03650d..00000000 --- a/docs/plugins/README.md +++ /dev/null @@ -1,393 +0,0 @@ -# Rozszerzalność projektu MEVA poprzez system pluginów - -System pluginów umożliwia rozszerzanie funkcjonalności systemu kontroli wersji poprzez uruchamianie zewnętrznych skryptów użytkownika w odpowiedzi na określone polecenia wywoływane przez narzędzie `meva`. - -Pluginy pozwalają na walidację, automatyzację i integrację zewnętrznych usług bez modyfikacji kodu źródłowego. - -## Spis treści - -- [Czym jest plugin?](#czym-jest-plugin) - - [Rodzaje pluginów](#rodzaje-pluginów) -- [Zdarzenia systemowe](#zdarzenia-systemowe) - - [pre-execute](#pre-execute) - - [post-execute](#post-execute) -- [Rejestracja pluginu](#rejestracja-pluginu) - - [Polecenie register](#polecenie-register) - - [Argumenty](#argumenty) - - [Opcje](#opcje) - - [Manualna edycja plików konfiguracyjnych](#manualna-edycja-plików-konfiguracyjnych) -- [Kontekst wywołania](#kontekst-wywołania) - - [Struktura pliku](#struktura-pliku) - - [Przykłady](#przykłady) - - [Polecenie config list – zdarzenie post-execute](#polecenie-config-list--zdarzenie-post-execute) - - [Polecenie init – zdarzenie pre-execute](#polecenie-init--zdarzenie-pre-execute) - - [Obiekt error](#obiekt-error) - - [Struktura obiektu](#struktura-obiektu) - - [Przykład obiektu error](#przykład-obiektu-error) -- [Struktura plików w folderze plugins/](#struktura-plików-w-folderze-plugins) - - [Pluginy lokalne](#pluginy-lokalne) - - [Pluginy globalne](#pluginy-globalne) - - [Pliki opisujące informacje o wywołaniu](#pliki-opisujące-informacje-o-wywołaniu) -- [Integracja z plikami konfiguracyjnymi](#integracja-z-plikami-konfiguracyjnymi) - - [Przykładowa konfiguracja](#przykładowa-konfiguracja) - - [Dostępne opcje](#dostępne-opcje) - -## Czym jest plugin? - -Plugin to samodzielny skrypt lub plik wykonywalny, który po zarejestrowaniu w naszym systemie jest wywoływany w określonym momencie. - -Pluginy mogą być napisane w różnych językach programowania, np. Python, JavaScript, PowerShell itp. - -Pluginy pozwalają m.in. na: - -- Walidację danych przed wykonaniem operacji, -- Przetwarzanie wyników po wykonaniu komend, -- Automatyzację zadań związanych z zarządzaniem repozytorium, -- Integrację z zewnętrznymi systemami, -- Inicjalizację i konfigurację projektów. - -Każdy plugin jest wywoływany z pojedynczym argumentem pozycyjnym, będącym ścieżką do pliku zawierającego kontekst wywołania, czyli metadane dotyczące danego polecenia (np. `init`, `commit` czy `push`). - -### Rodzaje pluginów - -Ze względu na lokalizację wyróżniamy dwa rodzaje pluginów: - -- Globalne: - - Zlokalizowane w folderze `~/.meva/plugins/`. - - Dostępne we wszystkich repozytoriach użytkownika. -- Lokalne: - - Zlokalizowane w folderze `/.meva/plugins/`. - - Specyficzne dla konkretnego repozytorium. - -Fizycznie plugin jest przechowywany w systemie w postaci następującej pary: - -- plik wykonywalny/zawierający kod źródłowy pluginu (w wybranym języku programowania), -- wpis w odpowiednim pliku konfiguracyjnym pluginów (położenie pliku odzwierciedla polecenie systemowe, którego dotyczy plugin, czyli np. pluginy uruchamiane dla zdarzeń związanych z poleceniem `commit` znajdują się w pliku `.meva/plugins/commit/plugins.json`). - -## Zdarzenia systemowe - -System pluginów obsługuje dwa zdarzenia `pre-execute` oraz `post-execute`. Dla każdego zdarzenia system obsługuje dowolną liczbę synchronicznie wykonywanych pluginów. - -### `pre-execute` - -- **Kiedy:** Zdarzenie to występuje przed wykonaniem głównej operacji systemu określonej poprzez polecenie, np. `init`, `commit`, `push`. -- **Zastosowanie:** Walidacja, przygotowanie danych, sprawdzenie warunków wstępnych. -- **Zachowanie:** Jeśli plugin zwraca błąd, główna operacja nie zostanie wykonana. - -### `post-execute` - -- **Kiedy:** Zdarzenie to występuje po pomyślnym wykonaniu głównej operacji. -- **Zastosowanie:** Raportowanie, powiadamianie, automatyzacja powtarzalnych czynności, inicjalizacja dodatkowych plików. -- **Zachowanie:** Błąd pluginu nie wpływa na status głównej operacji (przerywa jedynie wykonanie pozostałych zarejestrowanych pluginów). - -## Rejestracja pluginu - -Rejestracja, czyli dodanie nowego pluginu do systemu MEVA, może odbyć się na dwa sposoby: - -1. Poprzez dedykowane polecenie systemu, -2. Poprzez ręczną edycję plików konfiguracyjnych. - -### Polecenie `register` - -Operację rejestracji nowego pluginu umożliwia polecenie `register`: - -```bash - meva plugins register [OPTIONS] --name --command --event --order -``` - -#### Argumenty - -- `` – ścieżka do skryptu/pliku wykonywalnego, który zostanie skopiowany do repozytorium. - -#### Opcje - -- `-f, --file ` – ścieżka względna określająca położenie skryptu wewnątrz repozytorium (przykładowo wewnątrz folderu `.meva/plugins/commit/`). - - Pozwala na tworzenie zagnieżdżonej struktury plików (ułatwia logiczne grupowanie pluginów użytkownikowi systemu). - - Ścieżki względne rozpoczynające się od `./meva/plugins/.invocations/` są niedozwolone. -- `-s, --scope ` – zakres pluginu. - - Dostępne wartości to lokalny (`local`) oraz globalny (`global`). -- `-n, --name ` – nazwa pluginu. - - Może być różna od nazwy pliku określonego przez opcję `file`. - - Musi być unikatowa wśród wszystkich pluginów zarejestrowanych dla danego polecenia i zdarzenia. -- `-d, --description ` – opis pluginu. - - Pełni on jedynie rolę informacyjną dla użytkownika naszego systemu. -- `-c, --command ` – typ komendy (w formacie kebab-case). - - Możliwe wartości to na przykład: `init`, `config-get`, `add`. -- `-e, --event ` – typ zdarzenia, dla którego będzie uruchamiany plugin (w formacie kebab-case). - - Możliwe wartości: `pre-execute` i `post-execute`. -- `-o, --order ` – kolejność uruchomienia pluginu w ramach danego polecenia i zdarzenia systemowego. - - Wszystkie pluginy zarejestrowane na to samo zdarzenie dla danego polecenia uruchamiane są synchronicznie w kolejności określonej przez pole `order`. - - Wartość `order = 1` oznacza, że plugin zostanie uruchomiony jako pierwszy (odpowiednio większy `order` oznacza, że plugin zostanie uruchomiony później). - - Wszystkie pluginy zarejestrowane dla danego polecenia systemowego powinny mieć unikalną wartość `order`. - - W przypadku próby zarejestrowania pluginu dla polecenia, dla którego występuje już plugin o określonej wartości `order`, operacja zakończy się błędem. -- `-t, --timeout ` – limit czasu wykonania w milisekundach (liczba całkowita). - - W przypadku braku wartości czas wykonania nie jest ograniczony z góry (rekomendowane dla pluginów pobierających dane od użytkownika, np. z konsoli). -- `-D, --disabled` – rejestracja pluginu jako wyłączonego. - - Wyłączony plugin nie będzie uruchamiany dla danego zdarzenia systemowego. -- `-i, --interpreter ` – opcjonalny interpreter używany do uruchamiania skryptu (np. `python3`). - - W przypadku braku wartości system będzie bazował na rozszerzeniu pliku z kodem źródłowym. -- `-h, --help` – wyświetla pomoc. -- `-V, --version` – wyświetla wersję. - -### Manualna edycja plików konfiguracyjnych - -Użytkownik może zarejestrować plugin poprzez edycję pliku `plugins.json` dla określonego zdarzenia systemowego. Przykładowo, w celu manualnego dodania nowego pluginu dla polecenia `commit`, należy zmodyfikować plik `.meva/plugins/commit/plugins.json`. - -Przykładowa zawartość pliku `plugins.json`: - -```json -[ - { - "name": "validate-commit-message", - "description": "This python script validates if the commit message contains task number eg. #123 - minor changes", - "file": "validators/validate-commit-message.py", - "event": "pre-execute", - "order": 1, - "timeout": 500, - "enabled": true, - "interpreter": "python" - }, - { - "name": "another-plugin", - "description": "This is another example to show the file structure clearly", - "file": "validators/another-plugin.py", - "event": "post-execute", - "order": 2, - "timeout": null, - "enabled": false, - "interpreter": "python" - } -] -``` - -Dodatkowo, należy przekopiować plik zawierający kod źródłowy do folderu `.meva/plugins/commit/`. Dla pierwszego pluginu z przykładowej zawartości pliku `plugins.json` będzie to: `.meva/plugins/commit/validators/validate-commit-message.py`. - -## Kontekst wywołania - -Każde wywołanie pluginu odbywa się w oparciu o plik w formacie **JSON**, który zawiera informacje o aktualnym stanie, przekazywanych danych oraz błędach. -Plik ten przekazywany jest do skryptu w momencie uruchomienia i pozwala mu zareagować w odpowiedni sposób na zdarzenie. - -### Struktura pliku - -Plik kontekstu zawiera cztery główne pola: - -- **`context`** – informacje stałe, wspólne dla wszystkich wywołań. - Zawiera m.in. typ komendy, typ zdarzenia, znacznik czasu oraz katalog roboczy. - -- **`pre-payload`** – dane wejściowe specyficzne dla danego polecenia. - To pole występuje zawsze, niezależnie od zdarzenia. - -- **`post-payload`** – dane wyjściowe specyficzne dla danego polecenia. - - - Dla zdarzeń `pre-execute` wartość **zawsze** jest `null`. - - Dla zdarzeń `post-execute` zawiera dane zwracane przez wykonane polecenie. - -- **`error`** – obiekt opisujący błąd. - Wypełniany jest przez skrypt pluginu, a następnie sprawdzany przez silnik pluginów. - Umożliwia zgłaszanie zarówno błędów technicznych, jak i biznesowych. - -Dzięki takiemu ujednoliconemu formatowi skrypty pluginów mają zawsze dostęp do niezbędnych danych wejściowych, a silnik pluginów może w spójny sposób obsługiwać zarówno poprawne wyniki, jak i błędy. - -### Przykłady - -#### Polecenie `config list` – zdarzenie `post-execute` - -```json -{ - "context": { - "command": "config-list", - "event": "post-execute", - "timestamp": "2025-09-21T15:09:27.770490400Z", - "working_dir": "C:\\meva\\target\\debug" - }, - "pre-payload": { - "config_file": "C:\\meva\\target\\debug\\.meva\\mevaconfig" - }, - "post-payload": { - "key_values": [ - ["plugins.enabled", "true"], - ["plugins.collect_logs", "true"], - ["plugins.timeout_ms", "500"] - ] - }, - "error": null -} -``` - -#### Polecenie `init` – zdarzenie `pre-execute` - -```json -{ - "context": { - "command": "init", - "event": "pre-execute", - "timestamp": "2025-09-21T20:34:23.091035200Z", - "working_dir": "." - }, - "pre-payload": { - "initial_branch": "master" - }, - "post-payload": null, - "error": { - "code": "BUSINESS_LOGIC_ERROR", - "message": "A business rule was violated during plugin execution", - "details": "Optional additional context or stack trace" - } -} -``` - -### Obiekt `error` - -Pole `error` w pliku kontekstu pozwala skryptowi przekazać szczegółowe informacje o błędach napotkanych podczas wykonania. -Silnik pluginów wykorzystuje je **dopiero wtedy**, gdy proces uruchamiający skrypt zakończy się kodem różnym od `0` (czyli wystąpi błąd). - -W przypadku poprawnego wykonania (`exit status = 0`) zawartość pola `error` nie jest walidowana przez silnik. - -#### Struktura obiektu - -Obiekt `error` jest zdefiniowany w formacie JSON i zawiera następujące pola: - -- **`code`** _(string, wymagane)_ - Kod błędu identyfikujący jego typ. Może przyjmować wartości takie jak np.: - - - `BUSINESS_LOGIC_ERROR` – naruszenie reguły biznesowej, - - `VALIDATION_ERROR` – nieprawidłowe dane wejściowe, - - `RUNTIME_ERROR` – błąd wykonania (np. problem z dostępem do pliku). - -- **`message`** _(string, wymagane)_ - Krótki, czytelny opis błędu, przeznaczony do logów lub komunikatów użytkownika. - -- **`details`** _(string, opcjonalne)_ - Szczegółowe informacje diagnostyczne – np. fragment stosu wywołań, dodatkowy kontekst lub wskazówki do debugowania. - -Dzięki temu mechanizmowi możliwe jest precyzyjne raportowanie zarówno błędów technicznych, jak i naruszeń reguł biznesowych, a logi pluginów pozostają spójne i czytelne. - -#### Przykład obiektu `error` - -```json -"error": { - "code": "VALIDATION_ERROR", - "message": "Missing required configuration parameter", - "details": "Parameter 'plugins.enabled' not found in configuration file" -} -``` - -## Struktura plików w folderze `plugins/` - -Układ katalogów w folderze `plugins/` jest analogiczny w obu lokalizacjach – różni się jedynie miejscem przechowywania: - -- **Pluginy lokalne** – w repozytorium, wersjonowane razem z projektem. -- **Pluginy globalne** – w katalogu użytkownika, dostępne dla wszystkich repozytoriów na danej maszynie. - -### Pluginy lokalne - -Przechowywane w repozytorium – dzięki temu są wersjonowane razem z projektem i dostępne dla wszystkich osób pracujących w tym repozytorium. - -```txt -/.meva/ -└── plugins/ - ├── commit/ - │ ├── plugins.json - │ ├── validate_commit_message.py - │ └── ... - ├── config-set/ - │ ├── plugins.json - │ ├── check_if_secret_key_not_included.py - │ └── ... - ├── config-unset/ - │ ├── plugins.json - │ └── ... - └── ... -``` - -### Pluginy globalne - -Instalowane w katalogu użytkownika – dostępne dla wszystkich repozytoriów na danej maszynie. -Są przydatne np. do wymuszania organizacyjnych reguł (formatowanie kodu, walidacja commitów). - -```txt -~/.meva/ -└── plugins/ - ├── commit/ - │ ├── plugins.json - │ ├── validate_commit_message.py - │ └── ... - ├── config-set/ - │ ├── plugins.json - │ ├── check_if_secret_key_not_included.py - │ └── ... - ├── config-unset/ - │ ├── plugins.json - │ └── ... - └── ... -``` - -### Pliki opisujące informacje o wywołaniu - -Każde uruchomienie pluginu generuje w katalogu `.invocations/` zestaw plików pomocniczych, które umożliwiają analizę działania i debugowanie. -Struktura odzwierciedla rodzaj polecenia i datę/godzinę wywołania. - -```txt -.meva/ -└── plugins/ - └── .invocations/ - ├── commit/ - │ ├── 20250828-202222/ - │ │ ├── invocation.log - │ │ ├── pre-execute-context.json - │ │ ├── post-execute-context.json - │ │ ├── stdout.log - │ │ └── stderr.log - │ └── ... - └── ... -``` - -Zawartość katalogu wywołania: - -- `invocation.log` - Główny log wywołania – zawiera ogólne informacje o uruchomieniu pluginów. - -- `pre-execute-context.json` - Kontekst wywołania dla zdarzenia `pre-execute`. - Zawiera stałe pola (`context`), `pre-payload` oraz ewentualny obiekt `error`. - Pole `post-payload` zawsze ma wartość `null`. - -- `post-execute-context.json` - Kontekst wywołania dla zdarzenia `post-execute`. - Oprócz pól z `pre-execute` zawiera również `post-payload` z danymi wynikowymi. - -- `stdout.log` - Standardowe wyjście (`stdout`) ze skryptów pluginów. - -- `stderr.log` - Standardowe wyjście błędów (`stderr`) ze skryptów pluginów. - -Dzięki takiemu układowi możliwe jest łatwe odtworzenie i przeanalizowanie każdego wywołania – od danych wejściowych (`pre-execute-context.json`), przez dane wynikowe (`post-execute-context.json`), aż po logi i ewentualne błędy. - -## Integracja z plikami konfiguracyjnymi - -Działanie pluginów można kontrolować poprzez wpisy w plikach konfiguracyjnych systemu MEVA. -W sekcji `[plugins]` definiowane są podstawowe parametry, które wpływają na uruchamianie oraz logowanie pluginów. - -### Przykładowa konfiguracja - -```toml -[plugins] -enabled = true -collect_logs = true -``` - -### Dostępne opcje - -- `enabled` (_boolean_): - Określa, czy mechanizm pluginów jest aktywny. - - `true` - pluginy są uruchamiane zgodnie z rejestracją, - - `false` - wszystkie pluginy są ignorowane (lokalne i globalne). -- `collect_logs` (_boolean_): - Kontroluje zapisywanie logów z uruchomień pluginów do katalogu `.invocations/`. - - `true` - pliki `invocation.log`, `stdout.log`, `stderr.log` oraz konteksty (`*-context.json`) są zapisywane, - - `false` - logi nie są przechowywane (działanie pluginów jest wtedy trudniejsze do debugowania). - -Dzięki integracji z plikami konfiguracyjnymi możliwe jest centralne sterowanie zachowaniem pluginów – zarówno lokalnie (w repozytorium), jak i globalnie (w katalogu użytkownika). - -**_Uwaga_**: Jeśli dana opcja występuje zarówno w konfiguracji **globalnej**, jak i **lokalnej**, **pierwszeństwo ma konfiguracja lokalna**.