Skip to content
Merged
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ cd generator && cargo build --release

## Fixture definition format

Each `.json` file describes a git repo scenario:
Each `.json` file describes a git repo scenario. Add `$schema` for editor autocomplete and validation:

```json
{
"$schema": "https://raw.githubusercontent.com/FerrFlow-Org/Fixtures/main/schema/fixture.schema.json",
"meta": {
"name": "monorepo-two-packages",
"description": "Two packages both touched in the same commit get independent bumps"
Expand Down Expand Up @@ -257,6 +258,8 @@ Create branches from specific points and optionally merge them back:
│ ├── types.rs # Definition structs and deserialization
│ ├── tree.rs # Incremental tree builder for bulk generation
│ └── rng.rs # Deterministic RNG for synthetic commits
├── schema/
│ └── fixture.schema.json # JSON Schema for fixture definitions
├── fixtures/
│ └── examples/ # Example definitions for reference
└── .github/
Expand Down
240 changes: 240 additions & 0 deletions schema/fixture.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/FerrFlow-Org/Fixtures/main/schema/fixture.schema.json",
"title": "FerrFlow Fixture Definition",
"description": "Declarative definition for generating a git fixture repository.",
"type": "object",
"required": ["meta"],
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string",
"description": "JSON Schema reference for editor support."
},
"meta": {
"type": "object",
"description": "Metadata about this fixture.",
"required": ["name", "description"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Unique identifier for this fixture (used as directory name)."
},
"description": {
"type": "string",
Comment on lines +21 to +25

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meta.name and meta.description are validated as non-empty in generator/src/validate.rs (it errors on empty strings), but the schema currently allows empty strings. Consider adding minLength: 1 to both fields so editor validation matches runtime validation.

Suggested change
"type": "string",
"description": "Unique identifier for this fixture (used as directory name)."
},
"description": {
"type": "string",
"type": "string",
"minLength": 1,
"description": "Unique identifier for this fixture (used as directory name)."
},
"description": {
"type": "string",
"minLength": 1,

Copilot uses AI. Check for mistakes.
"description": "Human-readable description of the scenario. Copied into .expect.toml."
},
"default_branch": {
"type": "string",
"description": "Default branch name for the generated repo. Defaults to git's init.defaultBranch."
}
}
},
"config": {
"type": "object",
"description": "FerrFlow configuration file to place in the generated repo.",
"required": ["content"],
"additionalProperties": false,
"properties": {
"content": {
"type": "string",
"description": "Raw content of the config file."
},
"format": {
"type": "string",
"enum": ["json", "toml", "json5"],
"default": "json",
"description": "Config file format. Determines the default filename if 'filename' is not set."
Comment on lines +46 to +48

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config.format is restricted to json|toml|json5, but the generator code explicitly supports unknown formats by falling back to ferrflow.json (see resolve_unknown_format_falls_back_to_json test). With the current schema, definitions that work at runtime (e.g. format: "yaml") will be flagged invalid by editors. Consider removing the enum constraint and instead using examples/description to document the known values, or otherwise allow arbitrary strings.

Suggested change
"enum": ["json", "toml", "json5"],
"default": "json",
"description": "Config file format. Determines the default filename if 'filename' is not set."
"default": "json",
"examples": ["json", "toml", "json5"],
"description": "Config file format. Known values include 'json', 'toml', and 'json5'. Determines the default filename if 'filename' is not set. Unknown values are allowed and fall back to the default 'ferrflow.json' filename behavior."

Copilot uses AI. Check for mistakes.
},
"filename": {
"type": "string",
"description": "Explicit filename for the config file. Overrides the format-derived default."
}
}
},
"packages": {
"type": "array",
"description": "Packages to set up in the generated repo.",
"items": {
"type": "object",
"required": ["name", "path", "initial_version"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Package name."
},
"path": {
"type": "string",
"description": "Relative path to the package directory. Use '.' for root-level packages."
},
"initial_version": {
"type": "string",
"description": "Initial semantic version (e.g. '0.1.0', '1.0.0')."
},
"tag": {
"type": "string",
"description": "Tag to create on the initial setup commit for this package."
}
}
}
},
"commits": {
"type": "array",
"description": "Commits to create on the default branch after initial setup.",
"items": {
"$ref": "#/$defs/commit"
}
},
"tags": {
"type": "array",
"description": "Tags to create at specific commit positions.",
"items": {
"type": "object",
"required": ["name", "at_commit"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Tag name (e.g. 'v1.0.0', 'core@v0.2.0')."
},
"at_commit": {
"type": "integer",
"description": "Index into the 'commits' array. Use -1 for the initial setup commit.",
"minimum": -1
}
}
}
},
"branches": {
"type": "array",
"description": "Branches to create from specific points.",
"items": {
"type": "object",
"required": ["name"],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Branch name."
},
"from": {
"type": "string",
"description": "Source branch to fork from. Defaults to the default branch."
},
"at_commit": {
"type": "integer",
"description": "Commit index to branch from (-1 = initial setup). If omitted, branches from the tip of 'from'.",
"minimum": -1
},
"commits": {
"type": "array",
"description": "Commits to add on this branch.",
"items": {
"$ref": "#/$defs/commit"
}
},
"merge": {
"type": "string",
"description": "If set, creates a merge commit back into the named branch after all commits are added."
}
}
}
},
"hooks": {
"type": "array",
"description": "Hook scripts to place in the generated repo.",
"items": {
"type": "object",
"required": ["path", "content"],
"additionalProperties": false,
"properties": {
"path": {
"type": "string",
"description": "Relative path for the hook file (e.g. 'hooks/pre-bump.sh')."
},
"content": {
"type": "string",
"description": "Content of the hook script."
}
}
}
},
"generate": {
"type": "object",
"description": "Bulk generation mode. Creates synthetic commits without listing them individually.",
"additionalProperties": false,
"properties": {
"packages": {
"type": "integer",
"minimum": 1,
"default": 1,
"description": "Number of packages to generate (1 = single-package repo)."
},
"commits": {
"type": "integer",
"minimum": 0,
"default": 100,
"description": "Number of synthetic commits to generate."
},
"seed": {
"type": "integer",
"minimum": 0,
"default": 42,
"description": "RNG seed for deterministic output."
}
}
},
"expect": {
"type": "object",
"description": "Expected output assertions. Written to .expect.toml for consumer test runners.",
"additionalProperties": false,
"properties": {
"check_contains": {
"type": "array",
"items": { "type": "string" },
"description": "Strings that must appear in the consumer's output."
},
"check_not_contains": {
"type": "array",
"items": { "type": "string" },
"description": "Strings that must not appear in the consumer's output."
},
"output_order": {
"type": "array",
"items": { "type": "string" },
"description": "Strings that must appear in order in the consumer's output."
},
"packages_released": {
"type": "integer",
"minimum": 0,
"description": "Expected number of packages released."
}
}
}
},
"$defs": {
"commit": {
"type": "object",
"required": ["message"],
"additionalProperties": false,
"properties": {
"message": {
"type": "string",
"description": "Commit message (should follow Conventional Commits format)."
},
"files": {
"type": "array",
"items": { "type": "string" },
"description": "Files to create/modify in this commit."
},
"merge": {
"type": "boolean",
"default": false,
"description": "If true, creates a merge commit instead of a regular commit."
}
}
}
}
}
Loading