feat(gitlab-sim): 🚀 ajout graph
This commit is contained in:
@@ -1,47 +1,187 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
gitlab-sim is a tool aiming to simulate and lint gitlab-ci pipelines locally and produce a graph.
|
||||
|
||||
## Commit Message Guidelines
|
||||
|
||||
**IMPORTANT: This project uses [Conventional Commits](https://www.conventionalcommits.org/) format.**
|
||||
|
||||
All commit messages must follow this format:
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat`: A new feature
|
||||
- `fix`: A bug fix
|
||||
- `docs`: Documentation only changes
|
||||
- `refactor`: Code change that neither fixes a bug nor adds a feature
|
||||
- `test`: Adding missing tests or correcting existing tests
|
||||
- `chore`: Changes to build process or auxiliary tools
|
||||
- `perf`: Performance improvements
|
||||
- `style`: Code style changes (formatting, missing semicolons, etc.)
|
||||
|
||||
**Scopes (commonly used):**
|
||||
- `auth`: Authentication/authorization changes
|
||||
- `security`: Security-related changes
|
||||
- `gui`: Web GUI changes
|
||||
- `api`: API changes
|
||||
- `readme`: README.md changes
|
||||
- `claude`: CLAUDE.md changes
|
||||
- `core`: Core library changes
|
||||
|
||||
**Breaking Changes:**
|
||||
Add `!` after type/scope for breaking changes:
|
||||
- `feat(api)!: remove deprecated endpoint`
|
||||
|
||||
**Note:** Always include a scope in parentheses, even for documentation changes.
|
||||
|
||||
When Claude Code creates commits, it will automatically follow this format.
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
`gitlab-sim` is a CLI tool that validates and lints `.gitlab-ci.yml` pipelines locally, without a GitLab server. It resolves `extends:` inheritance, fetches remote `include: project:` templates and `include: component:` catalog entries, then runs a set of lint rules over the fully-merged pipeline.
|
||||
|
||||
**Goals:** catch misconfigured pipelines early in a developer's workflow, before pushing to GitLab. Eventual goal: produce a visual graph of the pipeline DAG.
|
||||
|
||||
**Non-goals:** full GitLab CI emulation, job execution, secret resolution, or runner management.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Data flow
|
||||
|
||||
```
|
||||
model.Parse(path) // two-pass YAML → *Pipeline
|
||||
└─ resolver.ResolveIncludes // fetch project: / component: includes, merge
|
||||
└─ resolver.Resolve // resolve extends: inheritance chains
|
||||
└─ linter.Lint // run all lint rules → []Finding
|
||||
```
|
||||
|
||||
This order is intentional and must be preserved: includes must be merged before `extends:` resolution so that jobs defined in remote templates are available as base templates; `extends:` must be resolved before linting so that derived jobs carry their full merged definition.
|
||||
|
||||
### Package responsibilities
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `internal/model` | Data structures (`Pipeline`, `Job`, `Rule`, …) and YAML parser |
|
||||
| `internal/linter` | Lint rules; each rule returns `[]Finding` |
|
||||
| `internal/resolver` | `extends:` resolution and `include:` merging |
|
||||
| `internal/fetcher` | GitLab REST API client (token auth, file fetch) |
|
||||
| `internal/graph` | Mermaid graph generators (include dependencies, pipeline jobs) |
|
||||
| `cmd/gitlab-sim` | CLI entrypoint, flag parsing, output formatting |
|
||||
|
||||
### Two-pass YAML parser (`internal/model/parser.go`)
|
||||
|
||||
`ParseBytes` runs two `yaml.Unmarshal` passes over the same input:
|
||||
|
||||
1. **Raw pass** — into `map[string]yaml.Node` to collect every top-level key without type assumptions.
|
||||
2. **Typed pass** — into `*Pipeline` to decode reserved keys (`stages`, `variables`, `default`, `include`, `workflow`, `spec`) into typed structs.
|
||||
|
||||
Keys that survive the raw pass and are not in `ReservedKeys` are decoded individually as `Job` structs. This approach handles the open-ended job-name namespace without requiring a catch-all map in the typed struct.
|
||||
|
||||
**When adding a new top-level reserved key:** add it to `model.ReservedKeys` and add the corresponding typed field to `Pipeline`.
|
||||
|
||||
### `any` fields in `Job`
|
||||
|
||||
Many `Job` fields (e.g. `Artifacts`, `Cache`, `Image`, `Trigger`) are typed as `any` because GitLab CI allows both scalar and map forms. The linter type-asserts these at check time. Follow the existing pattern in `internal/linter/keywords.go`:
|
||||
|
||||
```go
|
||||
switch v := job.Artifacts.(type) {
|
||||
case map[string]any:
|
||||
// map form
|
||||
case nil:
|
||||
// not set
|
||||
}
|
||||
```
|
||||
|
||||
Never change an `any` field to a concrete struct type unless the GitLab CI spec guarantees only one form — doing so will silently drop the other form during YAML decode.
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Lint Rule
|
||||
|
||||
1. Add the check function to the appropriate file in `internal/linter/`:
|
||||
- **Per-job keyword constraint** → `keywords.go`, called from `checkJobKeywords`
|
||||
- **Cross-job graph rule** (needs, deps, extends) → dedicated file (`needs.go`, `dependencies.go`)
|
||||
- **Pipeline-level rule** → `linter.go`, called from `Lint`
|
||||
|
||||
2. Return `[]Finding` with the correct `Severity` (`Error` or `Warning`) and a `Job` field set when the finding is job-scoped, empty for pipeline-level.
|
||||
|
||||
3. Add a testdata fixture that triggers the new rule, and add it to the `validate` task in `Taskfile.yml`.
|
||||
|
||||
4. Document the rule in the lint rules table in `README.md`.
|
||||
|
||||
### Finding severity guide
|
||||
|
||||
| Severity | Use when |
|
||||
|---|---|
|
||||
| `Error` | The pipeline will definitely fail or behave incorrectly |
|
||||
| `Warning` | Deprecated usage, best-practice deviation, or a condition that *may* be wrong |
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Model Field
|
||||
|
||||
1. Add the field to `model.Job` with the correct `yaml:` tag.
|
||||
2. If the field has constrained values, add a validity map to `internal/linter/keywords.go` (e.g. `validJobWhen`).
|
||||
3. Add the check to `checkJobKeywords` and call it from there.
|
||||
4. Add both a valid and an invalid fixture case to `testdata/keywords_valid.yml` / `testdata/keywords_invalid.yml`.
|
||||
|
||||
---
|
||||
|
||||
## Go Code Style
|
||||
|
||||
- **Format:** `gofmt` (enforced via `go vet` in the `ci` task). Never commit unformatted code.
|
||||
- **Imports:** group stdlib / external / internal with a blank line between groups.
|
||||
- **Errors:** always wrap with context using `fmt.Errorf("doing X: %w", err)`. Use `%w` (not `%s`) to preserve the error chain.
|
||||
- **No panics** in library code (`internal/`). Panics are acceptable only in `main()` for truly unrecoverable startup failures — prefer returning errors.
|
||||
- **No global mutable state.** All configuration flows through function parameters or structs (see `fetcher.GitLabConfig`).
|
||||
- **Exported symbols** must have a doc comment. Unexported helpers do not need one unless the logic is non-obvious.
|
||||
- **Table-driven tests** are the default style for unit tests. Group cases in a `[]struct{ name, input, want }` slice and range over them.
|
||||
- **Dependencies:** prefer the standard library. The only current external dependency is `gopkg.in/yaml.v3`; keep it that way unless there is a compelling reason.
|
||||
- `go.sum` must always be committed alongside `go.mod`.
|
||||
|
||||
---
|
||||
|
||||
## Commit Message Guidelines
|
||||
|
||||
**IMPORTANT: This project uses [Conventional Commits](https://www.conventionalcommits.org/) format.**
|
||||
|
||||
All commit messages must follow this format:
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat`: A new feature
|
||||
- `fix`: A bug fix
|
||||
- `docs`: Documentation only changes
|
||||
- `refactor`: Code change that neither fixes a bug nor adds a feature
|
||||
- `test`: Adding missing tests or correcting existing tests
|
||||
- `chore`: Changes to build process or auxiliary tools
|
||||
- `perf`: Performance improvements
|
||||
- `style`: Code style changes (formatting, missing semicolons, etc.)
|
||||
|
||||
**Scopes:**
|
||||
- `linter`: changes to lint rules (`internal/linter/`)
|
||||
- `model`: data structures or YAML parser (`internal/model/`)
|
||||
- `resolver`: extends/include resolution (`internal/resolver/`)
|
||||
- `fetcher`: GitLab API client (`internal/fetcher/`)
|
||||
- `graph`: Mermaid graph generators (`internal/graph/`)
|
||||
- `cli`: CLI entrypoint (`cmd/gitlab-sim/`)
|
||||
- `testdata`: fixture files only
|
||||
- `docs`: README, CHANGELOG, or other documentation
|
||||
- `build`: Taskfile, go.mod, go.sum, CI config
|
||||
- `claude`: CLAUDE.md changes
|
||||
|
||||
**Breaking Changes:**
|
||||
Add `!` after type/scope for breaking changes (e.g. changed CLI flags, removed output fields):
|
||||
```
|
||||
feat(cli)!: rename --token to --api-token
|
||||
```
|
||||
|
||||
**Note:** Always include a scope in parentheses, even for documentation changes.
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
```bash
|
||||
task ci # full check: vet → test → build → validate (run before every commit)
|
||||
task build # compile the binary
|
||||
task test # run Go unit tests
|
||||
task lint-go # go vet only
|
||||
task validate # run the binary against all testdata fixtures
|
||||
task clean # remove build artifacts
|
||||
```
|
||||
|
||||
`task ci` must pass before any commit is created.
|
||||
|
||||
### Testdata fixtures
|
||||
|
||||
Every fixture in `testdata/` is run by `task validate`. Files whose expected behaviour is *clean* (exit 0) are listed with `ignore_error: false`; files that are expected to produce errors (exit 1) use `ignore_error: true`. Keep both categories; do not use `ignore_error: true` for a fixture that is supposed to be clean.
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables (for manual testing)
|
||||
|
||||
| Variable | Purpose |
|
||||
|---|---|
|
||||
| `GITLAB_TOKEN` | Personal access token (`read_api` scope) |
|
||||
| `CI_JOB_TOKEN` | CI/CD job token (when running inside a pipeline) |
|
||||
| `GITLAB_PRIVATE_TOKEN` | Legacy PAT name (lowest priority) |
|
||||
| `CI_SERVER_URL` | GitLab instance URL (takes precedence over `GITLAB_URL`) |
|
||||
| `GITLAB_URL` | GitLab instance URL fallback |
|
||||
|
||||
Token resolution order (first non-empty wins): `GITLAB_TOKEN` → `CI_JOB_TOKEN` → `GITLAB_PRIVATE_TOKEN`.
|
||||
URL resolution order: `--gitlab-url` flag → `CI_SERVER_URL` → `GITLAB_URL` → `https://gitlab.com`.
|
||||
|
||||
Reference in New Issue
Block a user