Skip to content
Open
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
4 changes: 4 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ jobs:
- name: Test
run: cargo test

- name: Check module size limits
working-directory: .
run: ./rust/scripts/check-module-size.sh

- name: Audit dependencies for known vulnerabilities
uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0
with:
Expand Down
9 changes: 8 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This is a command-line application. Source code is written in Rust and lives und
- `cargo clippy -- -D warnings` — must pass with zero warnings
- `cargo test` — must pass
- `cargo fmt --check` — must be clean
- `./rust/scripts/check-module-size.sh` — must pass

## Architecture

Expand Down Expand Up @@ -51,6 +52,12 @@ GoDaddy CLI is a Rust binary (edition 2024) built using:
- Prefer `crate::error::GddyError::{not_found,validation,auth,config,security,network,…}` (and `GddyError::from` for module client errors) so agents get stable `error.code` + top-level `fix`. Use `Err(cli_engine::CliCoreError::message("..."))` only for one-off cases that do not yet have a shared mapping.
- Streaming commands use `RuntimeCommandSpec::new_streaming` and emit events via `StreamSender`.

## Code File Structure (Required)

- Rust source files should mirror the CLI command tree structure. See
[Code file structure](./docs/code-structure.md) for specifics.
- CI fails any `.rs` file over 1000 lines. Split file exceeding this limit.

## Key Concepts

### Authentication
Expand All @@ -65,7 +72,7 @@ GoDaddy CLI is a Rust binary (edition 2024) built using:

### Extension security scanner

- Post-bundle regex scanner in `extension/mod.rs`.
- Post-bundle regex scanner in `extension/security.rs`.
- Rules SEC101–SEC115; uses `fancy-regex` for lookahead support.
- `scan_bundle(content, path) -> Vec<Finding>`, `is_blocked(findings) -> bool`.

Expand Down
31 changes: 31 additions & 0 deletions docs/code-structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Code file structure

`gddy`'s Rust source should mirror its CLI command tree. This doc explains the target shape and the CI check that enforces it, with a worked example of what to do — and what not to do.

## The rule

- One file per leaf subcommand, named after the subcommand (`domain/purchase.rs` for `gddy domain purchase`).
- One `mod.rs` per command group, containing *only* wiring: `mod` declarations, a `pub fn module()` (top level) or `pub(super) fn group()` (nested), and nothing that could stand alone as a unit of behavior.
- Shared cross-cutting helpers (an authenticated client, error-body rendering, money/format helpers, validation) live in a sibling `common.rs`, exposed at `pub(super)`/`pub(crate)` — not duplicated per subcommand file, and not left in `mod.rs`. If the `common.rs` file grows too large, like over 1000 lines of code, split it in to appropriately named modules per theme.
- A nested group (a subcommand that itself has subcommands, e.g. `domain nameservers set`) gets its own file exporting `pub(super) fn group() -> RuntimeGroupSpec`. If that group's own commands grow past the point a flat file can hold comfortably, promote it to a subdirectory with its own `mod.rs` plus one file per leaf, the same way a top-level module works.
- CI enforces a 1000-line ceiling on every hand-written `.rs` file — see `rust/scripts/check-module-size.sh`. There's no allowlist; if a file is over the line, split it before merging.

## Example

```
src/domain/
mod.rs # Module::new("Domains", ...) wiring only
common.rs # make_client, format_money, validate_domain_name, api_error
list.rs # `domain list`
get.rs # `domain get`
available.rs # `domain available`
suggest.rs # `domain suggest`
agreements.rs # `domain agreements`
quote.rs # `domain quote`
purchase.rs # `domain purchase`
nameservers.rs # `domain nameservers set` (nested group, flat file)
contacts.rs # `domain contacts ...` (nested group, flat file)
operation.rs # `domain operation status` (nested group, flat file)
```

`mod.rs` never contains a `CommandSpec`, a `clap::Args` struct, or an API call — it only assembles what the sibling files export. A reviewer can find "the `purchase` command" by opening exactly one file, and never has to scroll past unrelated commands to get there.
29 changes: 29 additions & 0 deletions rust/scripts/check-module-size.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Fails if any hand-written .rs file exceeds the line-count limit — see
# docs/code-structure.md for the file-layout convention this enforces.
#
# Scope: rust/src, every rust/tools/*/src, and rust/domains-client/src — every
# workspace member's hand-written source. Deliberately excludes rust/target
# (build output; nothing under it is committed source).
set -euo pipefail

rust_root="$(cd "$(dirname "$0")/.." && pwd)"
limit=1000
violations=0

while IFS= read -r -d '' file; do
lines=$(wc -l < "$file")
if [ "$lines" -gt "$limit" ]; then
echo " $file: $lines lines (limit $limit)"
violations=$((violations + 1))
fi
done < <(find "$rust_root/src" "$rust_root"/tools/*/src "$rust_root/domains-client/src" \
-name '*.rs' -print0 2>/dev/null)

if [ "$violations" -gt 0 ]; then
echo "ERROR: $violations file(s) over the ${limit}-line limit (shown above)."
echo "Split by subcommand/concern — see docs/code-structure.md."
exit 1
fi

echo "==> All .rs files are within the ${limit}-line limit"
Loading
Loading