Skip to content

Releases

gnr8 0.21.0

Choose a tag to compare

@github-actions github-actions released this 22 Oct 06:45

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.21.0 — 2026-10-22

Breaking

  • OpenApi31, OpenApi31Json, GoSdk, PySdk and TsSdk have a new public
    excluded_operations field, so a struct literal of one of them must set it. Build them with
    new() and the builder methods instead.

Added

  • OpenApi31, OpenApi31Json, GoSdk, PySdk and TsSdk accept .exclude(OperationSelector).
    The target leaves the matched operations out of its own output, with their per-operation policies
    and every schema only they reach; sibling targets still emit them. A published reference and a
    first-party client can come from one pipeline. A selector that matches no operation is a
    configuration error, and StaticDocs refuses a sibling SDK that excludes operations.

View this release in the changelog.

gnr8 0.20.0

Choose a tag to compare

@github-actions github-actions released this 19 Oct 18:52

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.20.0 — 2026-10-19

Breaking

  • Committed graph artifacts use schema version 3: provenance keeps the source file, and the graph
    retains its existing symbol identities. Source line spans and diagnostic line numbers are
    omitted. gnr8 changes upgrades schema-v1 and schema-v2 base artifacts before comparison and
    reports source locations from the current run. Live diagnostics retain lines.
  • Generated SDK READMEs no longer include aggregate operation and schema counters. Published
    diagnostics show file paths only and sort by file, code, message and symbol, with identical
    rendered diagnostics deduplicated. OpenAPI YAML enums and required fields use one block-sequence
    item per line, preserving value order. These outputs no longer change when source lines move.

Fixed

  • OpenAPI YAML quotes strings a YAML reader would otherwise resolve to a number or a merge: radix
    integers (0x1F, 0o17, 0b101), underscore-grouped digits (1_000) and <<, whether they
    appear as enum members, required fields or schema keys.

View this release in the changelog.

gnr8 0.19.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 13:17

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.19.0 — 2026-10-12

Breaking

  • Go extraction no longer publishes a request null that a custom decoder decides.
    encoding/json's rules say whether a field accepts an explicit null only for the null it
    decodes itself. gnr8 used them for every field, so a field whose UnmarshalJSON refuses null was
    published as nullable, and the SDKs offered a null the server rejects. A field is now published as
    not accepting null, and raises the new WARN diagnostic schema.nullability.unresolved, when its
    null goes to code gnr8 does not read:

    • every field of a type that implements json.Unmarshaler, its own method or one promoted from an
      embedded struct;
    • every field of a type decoded inside such a type's value, at any depth, whatever else decodes
      it;
    • a value field whose own type implements json.Unmarshaler (a pointer field is not: encoding/json
      sets it to nil without calling the pointee's method, and neither is time.Time);
    • a binding:"required" / validate:"required" json.RawMessage, which keeps a literal null as the
      bytes null that required passes. A required map was already not nullable, since required
      refuses its nil.

    The diagnostic names the type and the JSON field and is raised only where encoding/json's rules
    would have published the null; its schema is the schema id and its subject the JSON name. It
    counts only while its schema publishes an input answer: a DiagnosticPolicy asks of the graph it
    runs on, and the run reports it only if the finished graph still does, so it is dropped when only
    responses reach the schema or a transform removes it. It stays through every transform, so a later
    register_input_schema or request body brings it back into scope, and it follows a RenameType
    to the new name. State the answer in .gnr8 with
    ApiOverrides::force_nullable(schema, field, SchemaUse::Input) or force_non_nullable with the same
    arguments: on such a field either one is accepted, neither is redundant, and the diagnostic is
    retired. A DiagnosticPolicy that denies the code fails the pipeline on every null left unstated.
    There is no struct tag for this fact. A field that is no longer nullable keeps 0.18.1's rule: a
    Python None in an optional field is the absent key, so the SDK never sends the null.
    openapi.yaml drops null from each such property, and every generated model changes for it as
    for any field that stops accepting null: TypeScript drops | null, a required Python field drops
    Optional[…] and an optional one defaults to None instead of UNSET. Regenerate, then declare
    each field the diagnostic names.

View this release in the changelog.

gnr8 0.18.3

Choose a tag to compare

@github-actions github-actions released this 12 Oct 07:34

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.18.3 — 2026-10-12

Fixed

  • Generated CLI body help preserves field-level enum restrictions and request nullability, quotes
    ambiguous enum labels, and distinguishes container element unions from nullable containers.
    Body help no longer recalculates whole-graph directions for every command.
  • OpenAPI output intersects inline enums with field constraints, retains empty enum sets, and
    includes null in nullable enums. In 3.1 enum constrains every value, so a nullable property's
    member list without null rejected the null it admits. Consumers that read enum members as
    strings must now skip null. A constraint narrows an inline enum without discarding the
    member order SetEnumOrder gave it.
  • Fixed the generated CLI documentation's Body example closing fence.
  • Go extraction reports a field-scope oneof rule on a slice or map field as
    schema.metadata.unresolved instead of publishing an enum on the collection itself, which no
    non-null value satisfied (the validator panics on that rule). A oneof past dive and the element
    type's own values are unaffected.
  • OpenAPI import reads a {type: 'null'} arm of oneOf/anyOf as nullability. The 3.1
    nullable-$ref form gnr8 emits now imports as the referenced type, nullable, instead of a union
    with an untyped member that the Go SDK rejected. A $ref to a component written that way is
    nullable too.
  • OpenAPI YAML quotes ,, [, ], { and } anywhere in a flow-sequence item (enum members,
    tags, required, scopes), so a member such as a, b no longer splits in two.

View this release in the changelog.

gnr8 0.18.2

Choose a tag to compare

@github-actions github-actions released this 11 Oct 08:02

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.18.2 — 2026-10-11

Added

  • Retired flags. A CliRenameError may end in one long flag the command no longer takes:
    CliRenameError::new(["books", "get", "--book-id"], "books get <id>"). books get --book-id 1
    prints books get --book-id is now bookstore books get <id>, exits 2 and sends no request, in
    the Go and Python CLIs. Generation refuses a retired flag that the command binds, one after a
    topic or with no command, one after a path that extends a generated command, --help, -h and
    --version, and (Python) one that begins a bound flag, because argparse reads --deb as
    --debug.
  • checkRetired for hand-owned Go commands. Generated package cli always declares
    checkRetired(path, args []string) int. A hand-owned dispatcher calls it before it reports an
    unknown command, so the declared table also covers retired paths under hand-owned commands.
    Only a retired invocation beyond path matches. parseFlags calls it with the flag tokens when
    a parse fails, and reads the command path from the flag.FlagSet name, so it resolves only
    retired flags of that path. Generation now accepts retired invocations under root and topic
    owned commands.

Changed

  • Retired invocations resolve on the error path. The generated CLIs no longer match retired
    paths before dispatch. The Go CLI looks up the table only for an unknown command under the root,
    a topic or a sub-noun, an unexpected argument after a generated command that takes none, and a
    failed flag parse; the Python CLI only when argparse rejects the arguments. A retired invocation
    therefore never shadows a live command. When several match, the longest command path wins, then
    a retired flag over the path it follows, then the retired flag typed first. Order no longer
    matters, so generation accepts a retired path under a shorter one and refuses only a
    duplicate. A retired path that extends a generated command without arguments now yields to that
    command's --help. The message, exit code and output channel do not change. Generated Go names
    each command's flag.FlagSet after its full invocation (books get), and checkRetired
    replaces the undocumented checkRename.

View this release in the changelog.

gnr8 0.18.1

Choose a tag to compare

@github-actions github-actions released this 11 Oct 07:36

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.18.1 — 2026-10-11

Breaking

  • BuiltinTarget gains StaticDocs. Rust code that matches BuiltinTarget exhaustively needs
    an arm. The host/worker protocol is now version 9, so a worker and CLI cannot silently disagree
    about the stage-plan shape.
  • gnr8::facts::Constraints gains multiple_of and unique_items, and gnr8::graph::Param
    gains example.
    Code that builds either struct with a literal, an OpenApiFieldPatch's
    constraints included, must set the new fields or finish with ..Default::default().
  • Go extraction no longer reads enums:"…" / enum:"…" struct tags. That spelling belongs to
    another generator, and no Go runtime consumes it. A field or bound parameter that stated its enum
    only that way now publishes a plain string, with no enum and no diagnostic. State the enum with
    the validator's own rule instead: binding:"oneof=a b" or validate:"oneof=a b". Separate the
    members with spaces. That rule is the one source of an enum constraint.
  • generated/gnr8.graph.json is schema version 2. An imported parameter's validation keywords
    now live in its typed constraints instead of the raw schema the graph keeps, its scalar
    example moves into the new Param::example, Constraints carries multiple_of and
    unique_items, and an imported server no longer repeats the base path. Tools that read the
    artifact must accept version 2. gnr8 changes --base <ref> still reads a version 1 base, by the
    importer's own rules, so the first comparison after upgrading reports no change the API did not
    make. The rules below that leave a fact unknown apply only to what the base imported from an
    OpenAPI document: a node whose provenance file is a JSON or YAML document (openapi.yaml), or
    for servers, a base with such a node. Anything gnr8 extracted from source code is compared whole,
    so a fact added to it is reported:
    • A parameter is read through the importer's parameter rules. 0.18.0 did not import a Swagger 2
      parameter's multipleOf, so an imported parameter's multipleOf is unknown when the base
      states none; one the base states is compared. A field bound kept as the OpenAPI
      3.0 flag (minimum: 0 with exclusiveMinimum: "true", in a schema, a parameter's type or a
      response header's type) reads as the exclusive bound.
    • A fact a version 1 artifact could not hold of an imported field is unknown, not added:
      multipleOf and uniqueItems, an enum or an example the base does not state, and a plain
      string that is now a string enum. Every fact the base states is still compared. So on that
      first comparison, an enum or example newly added to an imported field whose base had none is
      not reported.
    • An imported field that names the same schema as before with $ref and now accepts null is
      unknown, not changed: 0.18.0 dropped the null such a schema admits (see Fixed).
    • A version 1 artifact never held the bounds of the schema an imported parameter names with
      $ref. A keyword the current graph holds tighter than the base states reads as the base's
      value, and so does an enum that is a subset of the base's; a keyword loosened or removed is
      still reported, pattern is compared as it is, and multipleOf as any imported parameter's
      is (above). So on that first
      comparison, a real tightening stated beside the $ref is not reported.
    • An imported base server whose URL is a current server's URL followed by the base graph's base
      path (https://api.example.com/v1 beside /v1, trailing slashes ignored) is the same server.
      A server set with OpenApiMetadata::server in a graph read from source code was sent as
      written in both versions, so moving it off the base path is reported.
  • A Python field that is optional and nullable can be sent as an explicit null again, the
    PATCH that clears a value. A dataclass field of that kind is typed Union[T, Unset] and defaults
    to UNSET, defined in the new unset.py every dataclass SDK carries (from <package>.unset import UNSET; falsy, with __bool__ typed Literal[False] so a type checker narrows on it,
    and UNSET as both repr and str). to_dict sends UNSET as no key and None as null, and
    from_dict reads an absent key as UNSET and a null as None, where both used to read None.
    Code that tests such a field with is None after decoding a reply must test is UNSET for an
    absent key. The generated CLI prints UNSET as null. A Pydantic model's field still reads
    None, and to_dict sends null when the field was set (model_fields_set) and omits it
    otherwise. An optional field that is not nullable is unchanged: its None is the absent key.
    Generated dataclass models.py and contract_test.py change for such fields, the generated CLI's
    output.py changes in every dataclass SDK with an object schema (it imports UNSET), and
    Pydantic models.py changes for every model with one.
  • Each SDK's README.md and reference.md are rewritten from the docs model that StaticDocs
    renders its pages from, built for that one SDK. README.md replaces the Package/module line
    with Import (the Go module path, the Python import package, the package.json name) and
    Install (go get, pip install with the distribution name pyproject.toml lists, npm install), and its <Language> quick start placeholder with ## Quick start: the first sampled
    operation's call, byte for byte as reference.md prints it, or, when none has one, a line saying
    that no operation has a sample call and that reference.md says why under each operation. An SDK with no package metadata
    says why it has no import and no quick start. Its Agent workflow's first step now points to
    reference.md for every operation, schema, error and credential, each with its sample.
    reference.md is one file holding every docs page as a section, one heading level down: the
    index (which lists every operation and schema, and the Errors and Authentication sections by
    name), each operation (parameters, bodies, responses, the call, typed-error and iterator samples
    for this SDK, the CLI invocation), each schema, the error catalog (The generated SDK surfaces a non-success status as its typed error, …) and authentication. Its Operations, Operation Documentation and Schemas tables are gone; it links only README.md, and a diagnostic is
    printed under its operation, or under the index section when it names none. The API title is
    printed as itself in both files, with any Markdown syntax in it escaped. Writing these files now
    builds the docs model, so with docs on (the default) an SDK target fails generation on what
    StaticDocs fails on — CoreError::InvalidExample for an invalid declared example,
    CoreError::DocsGen for a blank API title or group name or prose that breaks reference.md's
    structure — and refuses an OpenApiSchemaPatch that changes a field fact reference.md prints
    (see the next entry). .without_docs() writes neither file. Tools that parse either file must
    follow the new layout.
  • An existing OpenApi31 schema patch that sets a documented field fact now fails generation
    in any pipeline with an SDK target that writes its docs (the default) or a StaticDocs target: a
    constraint (multipleOf and uniqueItems included), enum, description, default or example set
    by an OpenApiFieldPatch is a configuration error naming the docs output, the target, the field
    and the facts, because the docs and their samples read the graph and would disagree with the
    published spec. Set the fact in the source or with a Transform that edits the field, or declare
    each SDK .without_docs(). A patch that only adds x-* extensions is unaffected.
  • gnr8 verify checks the samples an SDK's README.md and reference.md print, whether or
    not the pipeline declares StaticDocs: an SDK target that writes docs and has a package manifest
    gains a docs suite (<Language> docs samples), which compiles and runs each sample and fails when
    either file no longer prints it. A target with neither contract tests nor docs leaves verify
    nothing to check, as before. With nothing to check, verify now says no SDK contract tests or generated CLI help checks to run, and no docs samples — add an SDK, CLI or StaticDocs target to .gnr8/src/main.rs, and a run in which every suite skipped says no checks executed: every generated check was skipped.
  • Declared examples are the sample, and an invalid one stops generation. A field's example,
    a parameter's example, and an operation's first request or response MediaExample for the JSON
    media type the sample uses, are now the values the docs pages and the contract tests send and
    reply with. A body or reply that declares an example is that example, and is not built from its
    fields. Each declared example is checked against its input before anything is sampled, whether a
    sample uses it or not: its type (an integer within its type's width and sign, a uint64 above
    i64::MAX included, a float32 within the float32 range), the required fields, fields the schema does not declare, nullability and
    every constraint except pattern. An integral number is an integer: a declared 5.0 is accepted
    for an integer input and stated as 5. A field example is read as a value of the field's type,
    so it can state only a scalar: example:"a,b" on a []string field is not a list, and an
    imported array, object or null property example is reported as a diagnostic and not imported.
    An example that breaks its input fails generation with CoreError::InvalidExample, which names
    the file and place the example is declared in and the type, range or constraint it breaks
    (`example...
Read more

gnr8 0.18.0

Choose a tag to compare

@github-actions github-actions released this 10 Oct 17:42

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.18.0 — 2026-10-10

Breaking

  • CliTopic gains owned_commands. Older serialized configs remain readable because the field
    defaults to empty, but Rust code that constructs CliTopic with a struct literal, or matches it
    exhaustively, must account for it.
  • Generation refuses CLI configs that produced a broken CLI. Two owned commands that call one
    Go function, an OwnedCommand::function the generated CLI already uses, and a rename_error that
    reaches a live command are now generation errors (see Fixed). A config that generated with any of
    them must change.

Added

  • Hand-owned commands inside generated topics. CliTopic::owned_command names a Go command
    under a declared topic whose implementation is hand-owned. The topic's dispatcher calls
    run<Topic><Name> (or OwnedCommand::function), and its help page and typo hints list it first.
    Generation refuses a topic with no generated command, a name equal to one of the topic's verbs or
    sub-nouns, and topic owned commands on PySdk. Configs without the new field emit identical
    output.

Fixed

  • OwnedCommand::function must be an ASCII Go identifier. The dispatcher emits the function
    name as Go source, so generation now refuses a value that is not an identifier, is a keyword, or
    uses non-ASCII letters (gnr8 emits ASCII identifiers only), at the root and under a topic.
  • OwnedCommand::function must not be a name the generated CLI already uses. A declared name
    (complete, Run), an imported package (strings, json), a Go predeclared name (len,
    string), _ or init called gnr8's own code, shadowed it, or was not callable, so cli.go did
    not compile; generation now refuses it.
  • Two owned commands may not call one Go function. Root db-types and topic db command types
    both derive runDbTypes; generation now refuses that and an explicit shared function.
  • rename_error must not reach a live command. A retired path is matched before dispatch, so a
    path that was a live command or a prefix of one (books, help) made that command unreachable, a
    flag token (--help) hid help or version output, and one that extended a command taking arguments
    captured them. Generation now refuses these, and a retired path that an earlier one already
    matches, in the Go and Python CLIs.

View this release in the changelog.

gnr8 0.17.2

Choose a tag to compare

@github-actions github-actions released this 09 Oct 22:36

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.17.2 — 2026-10-10

Added

  • Generated Go CLI --help prints a Body section after Flags on every command that takes --body:
    one row per request-body field with its JSON type, required/optional, one of: values for an
    enum, and its description cut to 80 characters. Top-level objects and arrays of objects list their
    fields one level deep as parent.child/parent[].child, a repeated or recursive shape names the
    path that already lists it, and fields bound as flags through body_fields are left out.
  • help --json carries body: {schema, fields} for each command that takes --body, in generated
    Go and Python CLIs.

View this release in the changelog.

gnr8 0.17.1

Choose a tag to compare

@github-actions github-actions released this 09 Oct 06:32

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.17.1 — 2026-10-07

Added

  • gnr8 verify checks root, group, and every selected operation command's --help for generated
    Go and Python CLIs, with explicit skipped-tool reports.

Fixed

  • Go extraction carries supported validation bounds on named scalar and collection fields while
    preserving their schema references.
  • OpenAPI YAML quotes the schema type name 'null', so nullable reference and union arms remain
    string-valued schema types when parsed.

View this release in the changelog.

gnr8 0.17.0

Choose a tag to compare

@github-actions github-actions released this 03 Oct 17:47

Install with the archive assets below, or run: curl -fsSL https://raw.githubusercontent.com/oaiz-io/gnr8/main/scripts/install.sh | bash.

0.17.0 — 2026-10-02

Added

  • Parameter descriptions are a graph fact. A bound parameter's prose is the field's own doc
    comment (plain prose, no tag grammar), the imported spec's description, or
    DocumentOperation::parameter when the source has neither. A collision is a hard error. Generated
    CLI --help prints that prose on the flag.
  • Generated CLI output format flags. --format human|ai-friendly|json|jsonl, --json as the
    JSON shorthand, and {PROG}_FORMAT. A TTY defaults to human; anything else defaults to
    ai-friendly. --fields, -o/--output, -q/--quiet, --debug, -y/--yes and --no-input
    are reserved globals. Exit codes 0–6 name the caller's next action; transient HTTP statuses, a failed connection or a
    timeout are 6 ("retry later"), so a success body that does not decode or a failed -o write is 1.
    Python returns 130 on Ctrl-C. Errors print error: plus optional hints and a request id; under
    --json they are one object on stderr carrying the server's slug.
  • ai-friendly result envelopes. Off-TTY default output is a bounded summary whose first line
    names a versioned envelope written under .{program}/output/ (or {PROG}_OUTPUT_DIR): atomic
    0600 files, a .gitignore, a 100-file / 100 MB retention cap over envelopes and downloads that
    never deletes the files the current run wrote, and a preflight before the request. The summary
    reserves room for the next-page line and jq recipes; whether a result is a list is decided at
    generation time from the graph. --json prints the captured server body.
  • Go CLI library seam. Run(args, Options) is the exported entry. main.go stamps
    version/commit/date as variables so -ldflags -X can overwrite them.
    SdkCli::hand_owned_main() skips that file; SdkCli::owned_command names a root command whose
    implementation is hand-owned and never generated. Python rejects both — they are a Go seam.
  • Declared CLI command spec. SdkCli::topic names verbs, sub-nouns, positional identifiers,
    body-field flags, switch flags, @vN/@latest selectors, and confirmation severity.
    rename_error prints the replacement and exits 2 without sending a request. view names the
    preview fields for the rows of one schema, in declared order. Absent a spec, command names stay
    the kebab-case operation ids.
  • Paginated commands emit --cursor and merged page JSON. --limit / --all collect items
    into {itemsKey: […], "hasMore": …} instead of a bare array, so --json is not the last page's
    body. --page-size is a rename error naming --limit. A ConfigurePagination cursor policy
    seeds --cursor onto the request.
  • Help layout and help --json. A declared command spec prints Arguments, Examples, Output,
    See also, and Docs on --help. Generation fails when a spec command has no example.
    help --json prints the command spec, including each flag's type and enum members.
  • Human polish and shell completion. --color auto|always|never (with NO_COLOR / TERM=dumb),
    width truncation, {PROG}_PAGER/PAGER paging of long human TTY output, and a stderr progress
    line on paginated walks. completion bash|zsh|fish|powershell prints a script; hidden
    __complete answers static candidates from the spec plus live identifiers (one list call, 1s
    timeout).

Fixed

  • CLI shell recipes quote paths and cursors as single arguments, including shell metacharacters.
  • Repeated identical results keep separate envelopes. Failed atomic writes remove their temporary
    files, and binary downloads preserve their bytes and file envelopes.
  • Switch flags dispatch in both languages, validate collisions and operation inputs before emission,
    and use the alternate operation's Go parameter type and result shape. Paginated switches honor
    limits, retain server metadata, request the remaining size, and preserve only boundary cursors.
  • Each generated CLI ships its versioned result JSON Schema, checked against persisted envelopes.
  • Human errors retain the request id within six lines, including when the server sends many hints
    or embedded line breaks.
  • Machine help includes path, body, switch, and global flag types. JSON usage and authentication
    errors use the structured error renderer, and one large preview row cannot exceed the byte budget.
  • --fields projects JSON items while preserving page metadata and large integer values; -o -
    prints the complete response without an envelope.

Changed

  • Output is no longer unconditionally JSON. --format and --json are reserved globals; a
    parameter named json or format is a generation error, and so is one named q, o or y
    (the short spellings of --quiet, --output and --yes).
  • Per-flag prose is emitted. A parameter's description is the one graph fact --help prints for
    it. SdkCli is no longer a three-field type: it carries the program name, command scope, host,
    library seam, and the declared command spec.

View this release in the changelog.