Skip to content
Open
73 changes: 72 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,73 @@
# developers
# Developer Documentation

Developer documentation about all things Git-Mastery

This repository contains the source for the Git-Mastery developer docs site, built with Jekyll and the Just the Docs theme.

## Prerequisites

Install the following tools first:

- Ruby 3.2.2

| Note: the installation commands below are for macOS using Homebrew only. Adjust as needed for your OS and package manager.

```bash
brew install rbenv ruby-build
rbenv install 3.2.2
echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc
source ~/.zshrc
rbenv global 3.2.2
ruby -v
```

- Bundler

```bash
gem install bundler
```

## Run locally

From the repository root:

1. Install dependencies:

```bash
bundle install
```

2. Start the local docs server:

```bash
bundle exec jekyll serve --livereload
```

3. Open the site at:

```text
http://127.0.0.1:4000/developers/
```

Note: this repository uses `baseurl: "/developers"`, so the local path includes `/developers/`.

## Build for production

To generate a static build in `_site/`:

```bash
bundle exec jekyll build
```

## Authoring notes

- Add or edit docs in `docs/`.
- Use frontmatter fields like `title`, `parent`, and `nav_order` so pages appear correctly in navigation.
- Keep links and examples consistent with the current Git-Mastery workflows.

## Troubleshooting

- `bundle: command not found`: install Bundler with `gem install bundler`.
- Shell still reports wrong Ruby version: run `rbenv version` to confirm 3.2.2 is active; if not, run `rbenv global 3.2.2` and restart the terminal.
- Port `4000` already in use: run `bundle exec jekyll serve --port 4001`.
- Styling or content not updating: restart `jekyll serve` and hard refresh your browser.
216 changes: 216 additions & 0 deletions docs/app/command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
---
title: How to add a command
parent: App
nav_order: 1
---

# How to add a command

This guide walks through adding a new top-level command to the `gitmastery` CLI. The CLI uses [Click](https://click.palletsprojects.com/), a Python library for building command-line interfaces.

We'll use a simple `greet` command as an example throughout.

{: .warning }

> This is for demonstration purposes. The `greet` command is not a real feature and should not be merged into the codebase. When adding real commands, follow the same steps but implement the actual functionality needed.

---

## 1. Create the command file

Create a new file under `app/app/commands/`:

```
app/app/commands/greet.py
```

Every command is a Python function decorated with `@click.command()`. Use the shared output helpers from `app.utils.click` instead of `print()`.

```python
# app/app/commands/greet.py
import click

from app.utils.click import info, success


@click.command()
@click.argument("name")
def greet(name: str) -> None:
"""
Greets the user by name.
"""
info(f"Hello, {name}!")
success("Greeting complete.")
```

### Output helpers

| Helper | When to use |
| -------------- | -------------------------------- |
| `info(msg)` | Normal status messages |
| `success(msg)` | Command completed successfully |
| `warn(msg)` | Non-fatal issues or warnings |
| `error(msg)` | Fatal issues — exits immediately |

---

## 2. Register the command in `cli.py`

Open `app/app/cli.py` and add two things:

1. Import your command at the top.
2. Add it to the `commands` list in `start()`.

```python
# app/app/cli.py

# 1. Import
from app.commands.greet import greet # add this

# 2. Register
def start() -> None:
commands = [check, download, greet, progress, setup, verify, version] # add greet
for command in commands:
cli.add_command(command)
cli(obj={})
```

---

## 3. Verify it works locally

Run the CLI directly from source to confirm the command appears and works:

```bash
uv run python main.py greet Alice
```

Expected output:

```
INFO Hello, Alice!
SUCCESS Greeting complete.
```

Also check it shows in `--help`:

```bash
uv run python main.py --help
```

---

## 4. Add an E2E test

Every new command needs an E2E test. Create a new file under `tests/e2e/commands/`:

```
tests/e2e/commands/test_greet.py
```

```python
# tests/e2e/commands/test_greet.py
from pathlib import Path

from ..runner import BinaryRunner


def test_greet(runner: BinaryRunner, gitmastery_root: Path) -> None:
"""greet prints the expected message."""
res = runner.run(["greet", "Alice"], cwd=gitmastery_root)
res.assert_success()
res.assert_stdout_contains("Hello, Alice!")
```

### `RunResult` assertion methods

| Method | Description |
| --------------------------------- | ------------------------------------------ |
| `.assert_success()` | Asserts exit code is 0 |
| `.assert_stdout_contains(text)` | Asserts stdout contains an exact substring |
| `.assert_stdout_matches(pattern)` | Asserts stdout matches a regex pattern |

---

## 5. Run the E2E tests

Build the binary first, then run the suite:

```bash
# Build
uv run pyinstaller --onefile main.py --name gitmastery

# Set binary path (Unix)
export GITMASTERY_BINARY="$PWD/dist/gitmastery"

# Set binary path (Windows, PowerShell)
$env:GITMASTERY_BINARY = "$PWD\dist\gitmastery.exe"

# Run only your new test
uv run pytest tests/e2e/commands/test_greet.py -v

# Run the full E2E suite
uv run pytest tests/e2e/ -v
```

All tests should pass before opening a pull request.

---

## Command group (optional)

If you want to add a command that has subcommands (like `gitmastery progress show`), use `@click.group()` and register subcommands with `.add_command()`.

```python
# app/app/commands/greet.py
import click

from app.utils.click import info


@click.group()
def greet() -> None:
"""Greet people in different ways."""
pass


@click.command()
@click.argument("name")
def hello(name: str) -> None:
"""Say hello."""
info(f"Hello, {name}!")


@click.command()
@click.argument("name")
def goodbye(name: str) -> None:
"""Say goodbye."""
info(f"Goodbye, {name}!")


greet.add_command(hello)
greet.add_command(goodbye)
```

Register `greet` the same way in `cli.py`. The user then runs:

```bash
gitmastery greet hello Alice
gitmastery greet goodbye Alice
```

---

## Checklist

Before opening a pull request for a new command:

- [ ] Command file created under `app/app/commands/`
- [ ] Command registered in `app/app/cli.py`
- [ ] Command works locally with `uv run python main.py`
- [ ] E2E test file created under `tests/e2e/commands/`
- [ ] E2E tests pass with the built binary

{: .reference }

See [E2E testing flow](/developers/docs/app/e2e-testing-flow) for a full explanation of the test infrastructure and how to write more complex tests.
92 changes: 92 additions & 0 deletions docs/app/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: Configuration reference
parent: App
nav_order: 2
---

# Configuration reference

The app uses two JSON files to manage state: one for the Git-Mastery workspace and one per downloaded exercise.

## `.gitmastery.json`

Created by `gitmastery setup` in the Git-Mastery root directory.

| Field | Type | Description |
| ------------------ | -------- | ------------------------------------------- |
| `progress_local` | `bool` | Whether local progress tracking is enabled |
| `progress_remote` | `bool` | Whether progress is synced to a GitHub fork |
| `exercises_source` | `object` | Where the app fetches exercises from |

### `exercises_source`

Two source types are supported:

**Remote** (default):

```json
{
"type": "remote",
"username": "git-mastery",
"repository": "exercises",
"branch": "main"
}
```

**Local** (for co-developing `app` and `exercises`):

```json
{
"type": "local",
"repo_path": "/absolute/path/to/exercises"
}
```

With `type: local`, the app copies the local exercises directory into a temp directory at runtime. This means changes you make in your local `exercises/` clone are picked up immediately without needing to push to GitHub — useful when developing `app` and `exercises` together.

To point at a fork or a feature branch, change `username` or `branch` in the remote config.

---

## `.gitmastery-exercise.json`

Created per exercise by `gitmastery download`. Lives in the exercise root.

| Field | Type | Description |
| ----------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------- |
| `exercise_name` | `string` | Exercise identifier used for progress tracking |
| `tags` | `string[]` | Used for indexing on the exercise directory |
| `requires_git` | `bool` | If true, app checks Git installation and `user.name`/`user.email` before download |
| `requires_github` | `bool` | If true, app checks GitHub CLI authentication before download |
| `base_files` | `object` | Map of destination filename to source path in `res/`; these files are copied to the exercise root alongside `README.md` |
| `exercise_repo` | `object` | Controls what working repository the student receives |
| `downloaded_at` | `string` | ISO timestamp of when the exercise was downloaded |

### `exercise_repo`

| Field | Type | Description |
| ------------------- | -------- | ------------------------------------------------------------ |
| `repo_type` | `string` | One of `local`, `remote`, `ignore`, `local-ignore` |
| `repo_name` | `string` | Name of the subfolder created for the student |
| `init` | `bool` | Whether to run `git init` (used with `local`) |
| `create_fork` | `bool` | Whether to fork the remote repo to the student's account |
| `fork_all_branches` | `bool` | Whether all branches are included in the fork (remote only) |
| `repo_title` | `string` | Name of the GitHub repository to clone or fork (remote only) |

### `repo_type` values

| Value | Behaviour |
| -------------- | --------------------------------------------------------------- |
| `local` | Creates a local folder, optionally runs `git init` |
| `remote` | Clones or fork-and-clones a GitHub repository |
| `ignore` | No repository created; `exercise.repo` is a null wrapper |
| `local-ignore` | Creates a folder without `git init`; student runs it themselves |

## How commands use these files

| Command | Reads | Writes |
| ---------- | --------------------------- | ----------------------------------- |
| `setup` | — | `.gitmastery.json` |
| `download` | `.gitmastery.json` | `.gitmastery-exercise.json` |
| `verify` | `.gitmastery-exercise.json` | `progress/progress.json` |
| `progress` | `.gitmastery.json` | `.gitmastery.json` (on sync toggle) |
Loading