Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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