⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

Rewrite graph-cli in Rust#6282

Open
lutter wants to merge 75 commits intomasterfrom
lutter/gnd-cli
Open

Rewrite graph-cli in Rust#6282
lutter wants to merge 75 commits intomasterfrom
lutter/gnd-cli

Conversation

@lutter
Copy link
Collaborator

@lutter lutter commented Jan 19, 2026

This is all Claude-generated code, I haven't even looked at it or tried it. It's mostly here for informational purposes

@lutter lutter force-pushed the lutter/gnd-cli branch 2 times, most recently from 0f39307 to 021d897 Compare January 20, 2026 04:32
@lutter lutter force-pushed the lutter/gnd-cli branch 5 times, most recently from fbf5d36 to 64a077d Compare January 21, 2026 00:10
@lutter lutter marked this pull request as ready for review January 21, 2026 01:46
@dimitrovmaksim
Copy link
Member

dimitrovmaksim commented Jan 21, 2026

Couple of things I noticed for the commands that i'm familiar with:

  1. test
    • It expects matchstick to be a node_module installed via matchstick-as (the equivalent of graph-ts for matchstick). In the ts graph-cli we used a lib to download and execute binaries, so it probably got confused by that.
      EDIT: I now see that the binaryinstall lib was removed and the matchstick bin was directly fetched from the repo into node_modules/.bin/, but I don't see the test command doing it here
    • I would probably remove the binary path and leave only the Docker path, or at least make it the default (or phase out the test command completelly)
  2. add
    • By default the add command indexes events as entities. This caused some issues with duplicated entities and abi overwrites. If there were collision in event/entity names with existing events/entity names it would prepend the new entity name with the contract name. It seems that Claude has cut that whole logic off and just ignores collsions, unless the --merge-entities flag is passed. IMO this descision back then caused more trouble than benefit, so I would remove the code that touches the schema with the add command and leave this to the developer. Anyways it seems rare that entites match events 1:1.
    • the to_kebab_case function seems to not handle some cases correctly
      assert_eq!(to_kebab_case("ERC20Token"), "e-r-c20-token");
      (i would expect the result in this case to be either erc-20-token or erc20-token in the best case scenario). The TS cli uses the gluegun/loadash implementation for kebabCase

@lutter lutter force-pushed the lutter/gnd-cli branch 6 times, most recently from 8e5442c to 1728763 Compare January 23, 2026 21:52
lutter added 12 commits January 30, 2026 15:31
- Move dev command logic to commands/dev.rs module
- Add subcommand structure with stubs for all graph-cli commands:
  codegen, build, deploy, init, add, create, remove, auth, publish, test, clean
- Add version output showing graph-cli compatibility version (0.98.1)
- Preserve all existing dev command functionality and options

This is Phase 1 of the gnd CLI expansion to become a drop-in
replacement for the TypeScript graph-cli.
Add the `gnd clean` command to remove build artifacts and generated files.

- Removes `generated/` and `build/` directories by default
- Supports custom paths via --codegen-dir and --build-dir flags
- Includes unit tests for both success and missing directory cases
- Matches graph-cli clean command behavior
Add the `gnd auth` command to save deploy keys for Graph Node authentication.

- Stores keys in ~/.graph-cli.json (compatible with TS graph-cli)
- Supports custom node URLs via --node flag
- Defaults to Subgraph Studio URL
- Validates Studio deploy key format (32 hex chars)
- Includes unit tests for key storage and retrieval
Add a JSON-RPC client for communicating with Graph Node's admin API.
This client is used by the create and remove commands to register and
unregister subgraph names.

Features:
- HTTP/HTTPS support with protocol validation
- Optional access token authentication via Bearer header
- User-Agent header with gnd version
- 120 second timeout for long operations
- create_subgraph and remove_subgraph methods
Add commands to register and unregister subgraph names with a Graph Node.

Both commands:
- Use GraphNodeClient for JSON-RPC communication
- Support --node/-g flag for Graph Node URL (required)
- Support --access-token flag for authentication
- Automatically read deploy key from ~/.graph-cli.json if no token provided
- Include unit tests for CLI argument parsing

Usage:
  gnd create <name> --node <url> [--access-token <token>]
  gnd remove <name> --node <url> [--access-token <token>]
Add the output module with spinner functionality matching the TypeScript
graph-cli output format (gluegun/ora style). This provides:

- Spinner struct for progress indicators with colored output
- with_spinner() helper for wrapping long-running operations
- SpinnerResult for operations that can warn or fail
- Checkmark/cross/warning symbols matching TS CLI output

The module uses indicatif and console crates for terminal handling.
This commit adds the foundation for code generation functionality:

- codegen/typescript.rs: AST builders for generating TypeScript/AssemblyScript
  code (classes, methods, types, imports)
- codegen/types.rs: Type conversion utilities between GraphQL, Ethereum ABI,
  and AssemblyScript types
- codegen/schema.rs: SchemaCodeGenerator that generates entity classes from
  GraphQL schemas, matching the TS CLI output format

The schema code generator supports:
- Entity classes with constructor, save(), load(), loadInBlock()
- Field getters and setters with proper type conversions
- Nullable field handling with proper null checks
- Entity reference fields (stored as string IDs)
- Derived fields with loader classes
- Multiple ID field types (String, Bytes, Int8)
Implements codegen/abi.rs that generates AssemblyScript bindings from
Ethereum contract ABIs:

- Event classes with typed parameters and getters
- Call classes for function calls with inputs/outputs
- Smart contract class with typed call methods
- Tuple handling for nested struct types
- Support for indexed event parameters
- Reserved word escaping for AssemblyScript

The generated code matches the format of the TypeScript graph-cli.
Implements codegen/template.rs that generates AssemblyScript classes
for subgraph data source templates:

- Template class extending DataSourceTemplate
- Static create() method for creating new data sources
- Static createWithContext() method with context parameter
- Support for Ethereum (Address param) and file (cid param) templates
Implements formatter.rs that shells out to Prettier to format generated
TypeScript/AssemblyScript code:

- format_typescript() for strict formatting with error handling
- try_format_typescript() for graceful fallback when prettier unavailable
- Tries npx, pnpx, and global prettier installations
- is_prettier_available() utility to check for prettier
- Add ./src/ prefix to mapping file path in generated manifest (I7)
  The manifest referenced ./simple-contract.ts but the file was
  created at ./src/simple-contract.ts
- Replace buggy custom to_kebab_case with Inflector (A3)
  Custom implementation incorrectly converted ERC20 -> e-r-c20
  Inflector correctly produces erc20 (keeps acronyms together)
Previously, `gnd init --from-contract` would not create a networks.json
file, unlike `gnd init --from-example`. This meant users had to manually
create the networks.json configuration or experience errors when trying
to deploy to different networks.

Now, when scaffolding from a contract address, gnd creates a networks.json
file with the contract name, address, and start block configuration for
the specified network.

Issue: I5 from PR 6282 review feedback
lutter added a commit that referenced this pull request Feb 3, 2026
The init command's interactive mode now fetches contract information
(ABI, name, start block) from Etherscan/Sourcify immediately after
the user provides the network and contract address, before prompting
for remaining values.

This allows fetched values to be used as defaults:
- Contract name defaults to the fetched name
- Start block defaults to the fetched deployment block
- ABI prompt is skipped if fetch was successful

Addresses PR #6282 review feedback issue I1.
lutter added a commit that referenced this pull request Feb 3, 2026
The --spkg flag was for Substreams support which is not implemented
in the init command. Removing the unused flag to avoid confusion.

Addresses PR #6282 review feedback issue I4.
lutter added a commit that referenced this pull request Feb 3, 2026
The init command's interactive mode now fetches contract information
(ABI, name, start block) from Etherscan/Sourcify immediately after
the user provides the network and contract address, before prompting
for remaining values.

This allows fetched values to be used as defaults:
- Contract name defaults to the fetched name
- Start block defaults to the fetched deployment block
- ABI prompt is skipped if fetch was successful

Addresses PR #6282 review feedback issue I1.
lutter added a commit that referenced this pull request Feb 3, 2026
The --spkg flag was for Substreams support which is not implemented
in the init command. Removing the unused flag to avoid confusion.

Addresses PR #6282 review feedback issue I4.
The !== operator is not compatible with AssemblyScript versions below
0.20.0. Using != instead works correctly for null checks in both older
and newer AS versions.
…n add command

- Replace the --network flag with --network-file flag (default: networks.json)
  to match graph-cli's add command interface. The network is now always
  determined from the existing manifest's first data source.
- After adding a new data source, automatically update networks.json with
  the new contract's address and start block using update_networks_file().
The init command's interactive mode now fetches contract information
(ABI, name, start block) from Etherscan/Sourcify immediately after
the user provides the network and contract address, before prompting
for remaining values.

This allows fetched values to be used as defaults:
- Contract name defaults to the fetched name
- Start block defaults to the fetched deployment block
- ABI prompt is skipped if fetch was successful

Addresses PR #6282 review feedback issue I1.
The --spkg flag was for Substreams support which is not implemented
in the init command. Removing the unused flag to avoid confusion.

Addresses PR #6282 review feedback issue I4.
The test was failing because it expected `gnd build` to automatically
run codegen when generated files don't exist. While the spec says
build should "Run codegen (unless types already exist)", this feature
isn't implemented yet.

Fix the test to explicitly run `gnd codegen` before `gnd build`, which
matches what users would typically do and makes the test more explicit
about what it's testing.
&format!("Fetching contract info from {} on {}", address, network),
);

let contract_info = {
Copy link
Member

Choose a reason for hiding this comment

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

After the last changes, init interactive will fetch the contract info twice, first time from prompt.rs to populate the form/suggestions and the second time here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Woops .. fixed that. As far as I can tell, all the issues you found are now fixed except for anything to do with gnd test - not quite sure what to do there. I did remove schema editing from gnd add though.

Copy link
Member

Choose a reason for hiding this comment

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

I'll test the changes tomorrow and see if I will find something else. About test, I may try again to update matchstick and make it work, although I'm not sure there's anyone that can do a release(I don't have permissions anymore).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This would be a much bigger task, but I wonder if we shouldn't integrate matchstick into gnd - not sure how to prioritize it, but I am thinking that gnd init should also put down some sample tests and have the facilities to run tests, especially since it now also handles running Postgres etc. Maybe leave test out for now and do this separately. Seeing how matchstick hasn't had a release in more than 2 years, I wonder how much use it actually gets.

Copy link
Member

Choose a reason for hiding this comment

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

but I am thinking that gnd init should also put down some sample tests

Yeah, graph-cli actually does this, but I did not mention it as I wasn't sure if it's worth adding

This would be a much bigger task, but I wonder if we shouldn't integrate matchstick into gnd

I actually did that in my attempt to write graph-cli in Rust, and it was quite easy to have matchstick as a lib that can be executed directly from within the cli.

Seeing how matchstick hasn't had a release in more than 2 years, I wonder how much use it actually gets.

By the lack of open issues/comments in discord about it being broken/outdated, I would assume not much

Copy link
Member

@dimitrovmaksim dimitrovmaksim Feb 4, 2026

Choose a reason for hiding this comment

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

Couple of things:

  1. It seems the add command messes the addresses up when updating the manifest, removing the surrounding "" for all datasources.

  2. More of a nit: the next steps hint will be shown before prompting to add another contract/datasource and again after adding a new datasource, probably will do it on each itteration, I haven't tested with more than 2 datasources

Honestly the gnd flow feels way better than the current graph-cli flow. Can't see anything wrong with build and codegen atm, will have to test to deploy with gnd and see how this goes

After creating a subgraph from a contract, gnd now prompts "Add another
contract from <network>?" in interactive mode. This allows users to add
multiple contracts in a single session, similar to graph-cli behavior.

The flow:
1. User completes init --from-contract
2. gnd asks if they want to add another contract
3. If yes, prompts for address, name, start block
4. Runs the add command internally with merge_entities=true
5. Repeats until user says no

Implements I2 from the PR review feedback plan.
When the target directory already exists during `gnd init`, instead of
failing with an error, the user is now prompted to enter an alternative
directory name. This applies to all init modes: --from-contract,
--from-example, and --from-subgraph.

The new `resolve_directory_collision()` function in prompt.rs handles
the prompting loop, continuing until a non-existent directory is chosen.
When running `gnd init` interactively with a contract address, the
contract info was fetched twice: once in InitForm::run_interactive()
to get defaults for prompts, and again in init_from_contract() to
generate the scaffold.

Store the fetched ContractInfo in InitForm and pass it through to
init_from_contract() to skip the redundant network request.
@lutter lutter force-pushed the lutter/gnd-cli branch 3 times, most recently from 6c36b12 to c8b79ab Compare February 4, 2026 02:54
Consolidate duplicated code across build and codegen commands:

- Create manifest.rs with unified Manifest types and load_manifest()
- Create watch.rs with generic watch_and_run() for --watch mode
- Consolidate compile_data_source_mapping/compile_template_mapping
- Consolidate copy_abi/copy_template_abi into copy_abi_to_dir
- Add output_extension() helper for wasm/wast conditionals

Removes ~225 lines of duplicated code while improving maintainability.
Make `gnd init` generate scaffold files that match `graph-cli init` output
for better compatibility when --index-events is not set (placeholder mode).

Changes:
- manifest: Put address before abi in source, use event names for entities
  (not ExampleEntity), remove blank lines between event handlers
- schema: Use @entity(immutable: true), include first 2 event params with
  type comments, remove block fields from example entity
- mapping: Import contract class and all events, generate handlers for all
  events (first with full code, rest empty stubs), use correct ID pattern
  with Bytes.fromByteArray, add extended comments with callable functions
When manifest_path is a bare filename without directory component,
Path::parent() returns Some("") (empty string), not None. The old code
only handled None with unwrap_or_else, causing canonicalize() to fail.

Add manifest_dir() helper that filters empty paths and use it
consistently across build, add, and migrations commands.
Extract RESERVED_WORDS, handle_reserved_word(), and capitalize()
into a shared module to reduce code duplication across codegen.

Previously these utilities were duplicated in:
- codegen/schema.rs (RESERVED_WORDS with 47 items, handle_reserved_word)
- codegen/abi.rs (RESERVED_WORDS with 43 items, handle_reserved_word, capitalize)

The shared module uses a unified RESERVED_WORDS list that includes
all words from both lists (including "await" from abi.rs).
@fubhy
Copy link
Member

fubhy commented Feb 4, 2026

David is plowing through

@lutter
Copy link
Collaborator Author

lutter commented Feb 4, 2026

David is plowing through

With huge help from @dimitrovmaksim who's been doing the hard work of finding discrepancies with graph-cli

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants